# Redirect Checker & Bulk URL Status: Chains, Loops, 301 Map QA (`cygnix_labs/redirect-checker`) Actor

Bulk redirect checker and HTTP status code checker. Traces every hop (301/302/307/308, meta refresh) and flags redirect chains, loops, 404s, soft 404s, noindex targets and redirect-map mismatches after a site migration. First 20 URLs free, then $0.0008/URL.

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

## Pricing

$0.80 / 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.

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

## Redirect Checker & Bulk URL Status: Chains, Loops, 301 Map QA

**Answer first:** this actor is a **bulk redirect checker and HTTP status code checker** for SEO, site migrations and link cleanup. Give it a list of URLs, **old → new pairs**, a **redirect map CSV** (paste it, or a Google Sheets / Drive / Dropbox link) or a **sitemap**. It follows every hop itself (**301, 302, 303, 307, 308 and meta refresh**), records the status and Location of each one, and gives every URL a **verdict and a one-line fix**: `REDIRECT_CHAIN`, `LOOP`, `TOO_MANY_REDIRECTS`, `TEMPORARY_REDIRECT` (302 where a 301 belongs), `BROKEN_4XX`, `SERVER_ERROR`, `SOFT_404`, `REDIRECTS_TO_HOMEPAGE`, `TARGET_NOINDEX`, `MAP_MISMATCH` / `NOT_REDIRECTED` (the redirect doesn't land where your map says), `HTTPS_TO_HTTP`, `SSL_ERROR`, `DNS_ERROR` and more. It also writes a **fix list CSV** and a **flattened redirects CSV** (source → final URL for every chain, ready for your redirect rules). **The first 20 URLs of every run are free, then $0.0008 per URL ($0.80 per 1,000).** URLs that block the checker (401/403/429 or a bot challenge), invalid lines and duplicates are never billed.

| Quick facts | |
|---|---|
| Best for | Site migration QA (old → new redirect maps), redirect chain and loop cleanup, broken link / 404 checks, HTTP → HTTPS and www checks, checking old sitemaps or backlink targets after a relaunch |
| Input | URLs (one per line), `old new` pairs with optional expected status (`410`), a redirect map CSV (pasted or by link), or a sitemap URL |
| Output | One row per URL: `verdict`, `severity`, `chainText` (e.g. `301 → 302 → 200`), `hops`, `finalStatus`, `finalUrl`, `expectedUrl`, `matchesExpected`, `issues[]`, `fix`, `finalTitle`, `finalRobots`, `finalCanonical`, `chain[]` (every hop with status, Location and time) + `FIX_LIST.csv`, `FLATTENED_REDIRECTS.csv`, `SUMMARY` |
| Price | First 20 URLs free every run, then **$0.0008 per URL checked**. No start fee. Same price with the final-page check on or off |
| Needs | No API keys, no proxies, no login. Plain HTTP requests, max 4 at a time per host |
| Not included | JavaScript (`window.location`) redirects, crawling a whole site for links (use our Internal Link Audit for that), rank tracking |

**Try it now:** the input is prefilled with 4 lines, so the first run is free.

### Sample output (real run, 20 Oct 2026)

Build 0.1.2, run `zMx0ss7vuq6Ngmrui`, 15 input lines (3 of them old → new pairs), finished in about 8 seconds. All 15 were within the 20 free URLs, so the run cost **$0**. Full public dataset: [JSON](https://api.apify.com/v2/datasets/EduIAY6ZTshDpJXPf/items?format=json\&clean=1) · [CSV](https://api.apify.com/v2/datasets/EduIAY6ZTshDpJXPf/items?format=csv\&clean=1\&view=overview) · [FIX_LIST.csv](https://api.apify.com/v2/key-value-stores/YeSVaAmxkKLNtLNgY/records/FIX_LIST.csv)

| URL checked | Verdict | Status chain | Lands on |
|---|---|---|---|
| support.google.com/webmasters/answer/93633 | REDIRECT_CHAIN | 301 → 301 → 200 | developers.google.com/search/docs/crawling-indexing/301-redirects |
| docs.apify.com/platform/actors/running/ | REDIRECT_CHAIN + TEMPORARY_REDIRECT | 301 → 302 → 200 | docs.apify.com/actors/running |
| http://wikipedia.org | REDIRECT_CHAIN | 301 → 301 → 200 | https://www.wikipedia.org/ |
| httpbin.org/redirect/3 | REDIRECT_CHAIN + TEMPORARY_REDIRECT | 302 → 302 → 302 → 200 | httpbin.org/get |
| developers.google.com/search/docs/advanced/crawling/301-redirects | REDIRECT_OK | 301 → 200 | …/search/docs/crawling-indexing/301-redirects |
| twitter.com/apify → expected x.com/apify | MAP_OK | 301 → 200 | https://x.com/apify |
| http://github.com/about → expected https://github.com/about | MAP_OK | 301 → 200 | https://github.com/about |
| http://apify.com/store → expected apify.com/pricing (deliberately wrong) | MAP_MISMATCH | 301 → 200 | https://apify.com/store |
| http://bing.com | REDIRECT_OK + HTTP_FINAL, CANONICAL_ELSEWHERE | 301 → 200 | http://www.bing.com/ (canonical says https) |
| httpbin.org/status/404 | BROKEN_4XX | 404 | – |
| expired.badssl.com | SSL_ERROR | – | CERT_HAS_EXPIRED: certificate has expired |
| http://localhost:8080/x | PRIVATE_HOST (not requested, not billed) | – | – |

One row from that run (`docs.apify.com/platform/actors/running/`), trimmed:

```json
{
  "inputUrl": "https://docs.apify.com/platform/actors/running/",
  "verdict": "REDIRECT_CHAIN",
  "severity": "warning",
  "chainText": "301 → 302 → 200",
  "hops": 2,
  "finalStatus": 200,
  "finalUrl": "https://docs.apify.com/actors/running",
  "issueCodes": "REDIRECT_CHAIN, TEMPORARY_REDIRECT",
  "issueSummary": "REDIRECT_CHAIN: 2 hops: 301 → 302 → 200 | TEMPORARY_REDIRECT: 302 at https://docs.apify.com/actors/running/",
  "fix": "Multi-hop chain. Update the first rule to point straight at the final URL (one 301) and update internal links.",
  "chain": [
    { "step": 0, "url": "https://docs.apify.com/platform/actors/running/", "status": 301, "type": "redirect", "location": "https://docs.apify.com/actors/running/" },
    { "step": 1, "url": "https://docs.apify.com/actors/running/", "status": 302, "type": "redirect", "location": "https://docs.apify.com/actors/running" },
    { "step": 2, "url": "https://docs.apify.com/actors/running", "status": 200, "type": "final" }
  ],
  "charged": false
}
```

(These are public pages picked to show each verdict. A chain on someone else's site is just an observation, not a claim that it hurts them.)

### Verdicts and what to do

| Verdict | Severity | Meaning | Fix |
|---|---|---|---|
| `OK` / `REDIRECT_OK` / `MAP_OK` / `STATUS_OK` | ok | 200 directly, one clean 301/308 to a 200, lands where your map says, or returns the status you expected (e.g. 410) | – |
| `MAP_MISMATCH` | error | Redirects, but not to the URL in your map | Point the rule at the expected URL |
| `NOT_REDIRECTED` | error | Your map expects a redirect, the URL answers itself | Add the missing 301 |
| `STATUS_MISMATCH` | error | Status differs from the one you expected (e.g. expected 410, got 200) | Fix the rule or the expectation |
| `LOOP` / `TOO_MANY_REDIRECTS` | error | Never reaches a page (more than 10 hops by default; Googlebot follows up to 10) | Remove the conflicting rule |
| `BROKEN_4XX` / `SERVER_ERROR` | error | Ends on 404, 410, 5xx… | 301 to the closest live page, or 410 on purpose |
| `TARGET_NOINDEX` | error | Redirects to a noindex page (meta robots or X-Robots-Tag) | Remove noindex or pick another target |
| `DNS_ERROR` / `SSL_ERROR` / `TIMEOUT` / `CONNECTION_ERROR` | error | Host doesn't resolve, bad certificate, no answer | Fix DNS / certificate / server |
| `REDIRECT_CHAIN` | warning | 2+ hops | Point the first URL straight at the final one (see `FLATTENED_REDIRECTS.csv`) |
| `TEMPORARY_REDIRECT` | warning | A 302/303/307 in the chain | Use 301 or 308 for permanent moves |
| `REDIRECTS_TO_HOMEPAGE` | warning | Deep URL dumped on the homepage (Google often treats it as a soft 404) | Redirect to the closest matching page |
| `SOFT_404` | warning | 200 page that says "not found" | Return 404/410 or redirect properly |
| `META_REFRESH` | warning | Client-side refresh used as a redirect | Replace with a server 301 |
| `HTTPS_TO_HTTP` / `HTTP_FINAL` | warning / notice | Chain drops to HTTP, or ends on HTTP | Keep every hop on HTTPS |
| `CANONICAL_ELSEWHERE`, `CROSS_DOMAIN`, `NOINDEX` | notice | Final page's canonical points elsewhere; redirect leaves the domain; page is noindex | Check it is intended |
| `BLOCKED`, `PRIVATE_HOST`, `INVALID_INPUT` | notice | 401/403/429/bot challenge, internal address, unreadable line | **Never billed** |

### Input

Plain list, pairs and expected statuses in one box:

```json
{
  "urls": [
    "https://example.com/old-page",
    "https://old-site.com/blog/post-1 https://new-site.com/blog/post-1",
    "https://example.com/discontinued-product 410"
  ]
}
```

A migration redirect map (header names like old/new, source/destination, from/to and status are recognised; comma, semicolon or tab):

```json
{
  "redirectMapCsv": "old_url,new_url,status\n/blog/seo-tips,/guides/seo-tips,301\n/old-pricing,/pricing,301\n/spring-sale,,410",
  "baseUrl": "https://www.example.com"
}
```

Or a link: `"redirectMapUrl": "https://docs.google.com/spreadsheets/d/<id>/edit#gid=0"` (share as *Anyone with the link can view*). Or check an old sitemap after launch: `"sitemapUrl": "https://old.example.com/sitemap.xml", "maxUrls": 5000`.

Options: `maxRedirects` (10), `checkTargetPage` (on: noindex, canonical, soft 404, meta refresh), `followMetaRefresh` (on), `strictTrailingSlash` (off: `/page` = `/page/`), `browserUserAgent` (off), `maxConcurrency` (10), `requestTimeoutSecs` (20), `maxUrls` (1,000; up to 20,000).

### What a run costs

| URLs | Price |
|---|---|
| 20 | **$0** (free every run) |
| 100 | $0.064 |
| 1,000 | $0.784 |
| 10,000 | $7.98 |

Set **Maximum cost per run** in the run options and the actor stops exactly there (tested: cap $0.004 → 20 free + 5 billed URLs, then a clean stop with `stoppedAtSpendLimit: true` in `SUMMARY`).

### How it compares (live Apify Store prices, verified 20 Oct 2026)

| Actor | Price | Map QA (old → expected) | Notes |
|---|---|---|---|
| **This actor** | 20 free/run, then $0.0008/URL | **yes** (pairs, CSV, Sheets link) | Chains, loops, soft 404, noindex target, homepage dumps, fix list + flattened CSV |
| Broken link / bulk status checkers (several) | $0.001–$0.0025 per URL | no | Status codes and chains |
| Budget bulk status checkers | ~$0.00008–$0.0006/URL **+ $0.005–$0.035 start fee** | no | Cheaper at very large volume, start fee on small runs |
| Site migration redirect map validator | $0.005 per mapping | yes | 6× the price per row |

### For AI agents / MCP

Works as a tool in Claude, ChatGPT, Cursor or any agent through the [Apify MCP server](https://mcp.apify.com) (`cygnix_labs/redirect-checker`) or the API. Ask: *"Check these 300 old URLs redirect to the new site and give me the ones that don't."* The agent passes:

```json
{ "redirectMapUrl": "https://docs.google.com/spreadsheets/d/<id>/edit#gid=0", "maxUrls": 300 }
```

and reads `verdict`, `matchesExpected` and `fix` from the dataset, or the ready `FIX_LIST.csv`. Run it synchronously with `POST https://api.apify.com/v2/acts/cygnix_labs~redirect-checker/run-sync-get-dataset-items`.

### FAQ

**How do I check if a 301 redirect works?** Put the old URL and the new URL on one line (`old new`). `MAP_OK` means it lands there; `MAP_MISMATCH` shows where it really goes; `TEMPORARY_REDIRECT` means it's a 302, not a 301.

**How do I find redirect chains and loops in bulk?** Paste the URLs or your sitemap. Every row shows the full chain (`301 → 302 → 200`), and `FLATTENED_REDIRECTS.csv` lists source → final for each chain so you can rewrite the rules as single hops.

**Does it follow JavaScript redirects?** No, only HTTP redirects and meta refresh (no browser, which keeps it fast and cheap). Search engines also prefer server-side 301s.

**Why is a URL BLOCKED?** The server answered 401/403/429 or a bot challenge, so nothing could be checked. It's free. Try `browserUserAgent: true` or check from your own network.

**Does `/page` vs `/page/` count as a mismatch?** Not by default. Turn on `strictTrailingSlash` if it matters. Host case, default ports and `#fragments` are always ignored.

**Is it safe to run on a live site?** Yes: plain GET requests, max 4 at a time per host, no form posts. Bodies are only read for final HTML pages (first 1.5 MB).

### Changelog

- 0.1 (20 Oct 2026): first public release. Hop-by-hop tracing, map QA (pairs, CSV, Sheets/Drive/Dropbox links, relative paths), sitemap input (indexes, .gz), 20+ verdicts with fixes, fix list + flattened redirects CSVs, spend cap safe, private-address guard.

### More tools by Cygnix Labs

- [Internal Link Audit: Orphan Pages & Sitemap Crawl](https://apify.com/cygnix_labs/internal-link-audit): crawl your site for orphan pages and broken internal links.
- [Website SEO Audit & Site Audit Crawler](https://apify.com/cygnix_labs/site-seo-aeo-audit): page-level technical SEO + AEO score.
- [Content Decay Audit](https://apify.com/cygnix_labs/content-decay-audit): find pages losing traffic after a migration.
- Planning the migration? Our skill [Plan 301 Redirects for a Site Migration Without Losing SEO](https://www.agensi.io/skills/plan-301-redirects-for-a-site-migration-without-losing-seo) (Claude Code / Cursor / Codex) builds the redirect map this actor tests.

# Actor input Schema

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

One per line. A plain URL (https://example.com/old-page) is traced hop by hop. To test a redirect map, put the old and the expected new URL on one line: "https://old.com/a https://new.com/a" (space, comma, tab or -> between them). Add a 3-digit status to expect it, e.g. "https://site.com/old-promo 410". Domains without https:// are fine. Invalid lines get a free INVALID_INPUT row.

## `redirectMapCsv` (type: `string`):

Paste your migration redirect map: columns old URL, expected new URL, optional expected status. Header names like old/new, source/destination, from/to, url/target and status are recognised; comma, semicolon or tab separated. Relative /paths need the Base URL below.

## `redirectMapUrl` (type: `string`):

Link to the same CSV: a Google Sheets share link (Anyone with the link can view), Google Drive, Dropbox or any public CSV URL. Sheets/Drive/Dropbox links are converted to a direct CSV download.

## `baseUrl` (type: `string`):

Used when the map's old URLs are paths like /blog/post-1, e.g. https://www.example.com. Relative new URLs are always resolved against their old URL.

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

Optional. Check all URLs of an XML sitemap (sitemap indexes and .xml.gz followed), e.g. your OLD site's sitemap after a migration, to find URLs that now 404 or bounce through chains. Limited by Max URLs.

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

Upper limit of URLs checked in this run (after removing duplicates). Protects you from surprise costs on big sitemaps.

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

Stop following after this many hops and report TOO_MANY_REDIRECTS (Googlebot follows up to 10).

## `checkTargetPage` (type: `boolean`):

Reads the head of the final 200 page: title, meta robots / X-Robots-Tag noindex, canonical, meta refresh and soft-404 wording. Same price on or off.

## `followMetaRefresh` (type: `boolean`):

Treat <meta http-equiv=refresh> (delay up to 10 s) as a hop and flag it as META_REFRESH.

## `strictTrailingSlash` (type: `boolean`):

Off: /page and /page/ count as the same target. On: they must match exactly. Host case, default ports and #fragments are always ignored.

## `browserUserAgent` (type: `boolean`):

Off: requests identify as CygnixRedirectChecker. On: a desktop Chrome user agent, for sites that answer bots with 403. Blocked URLs (401/403/429/bot challenge) are never billed.

## `maxConcurrency` (type: `integer`):

URLs traced in parallel. Never more than 4 requests at once to one host.

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

Per hop. Slow hosts are reported as TIMEOUT.

## Actor input object example

```json
{
  "urls": [
    "http://apify.com/store",
    "https://docs.apify.com/platform/actors/running/",
    "https://httpbin.org/redirect/3",
    "http://github.com/about https://github.com/about"
  ],
  "maxUrls": 1000,
  "maxRedirects": 10,
  "checkTargetPage": true,
  "followMetaRefresh": true,
  "strictTrailingSlash": false,
  "browserUserAgent": false,
  "maxConcurrency": 10,
  "requestTimeoutSecs": 20
}
```

# Actor output Schema

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

No description

## `fixList` (type: `string`):

No description

## `flattened` (type: `string`):

No description

## `summary` (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 = {
    "urls": [
        "http://apify.com/store",
        "https://docs.apify.com/platform/actors/running/",
        "https://httpbin.org/redirect/3",
        "http://github.com/about https://github.com/about"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("cygnix_labs/redirect-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 = { "urls": [
        "http://apify.com/store",
        "https://docs.apify.com/platform/actors/running/",
        "https://httpbin.org/redirect/3",
        "http://github.com/about https://github.com/about",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("cygnix_labs/redirect-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 '{
  "urls": [
    "http://apify.com/store",
    "https://docs.apify.com/platform/actors/running/",
    "https://httpbin.org/redirect/3",
    "http://github.com/about https://github.com/about"
  ]
}' |
apify call cygnix_labs/redirect-checker --silent --output-dataset

```

## MCP server setup

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