# Broken Link Checker & Redirect Chain Checker (`paperlinenstudio/broken-link-checker`) Actor

Crawl a website and find every broken link (404, 5xx, dead domains, SSL errors, redirect loops) with the page and anchor text where it appears, or bulk-check URLs for status codes and full redirect chains. Bot-blocked links are flagged separately, not as false positives.

- **URL**: https://apify.com/paperlinenstudio/broken-link-checker.md
- **Developed by:** [Paper & Linen Studio](https://apify.com/paperlinenstudio) (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

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## 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

## Broken Link Checker & Redirect Chain Checker

Find every **broken link on your website**: 404s, 410s, 5xx server errors, dead domains (DNS), expired SSL certificates, redirect loops and timeouts. Each broken link comes with **the pages it appears on and its anchor text**, so you know exactly what to fix.

The Actor also works as a **bulk HTTP status code and redirect chain checker**. Paste a list of URLs (old URLs after a migration, backlinks, sitemap URLs, affiliate links) and get the status code, final URL and every redirect hop for each one.

**Free to use.** You only pay for the Apify platform usage of your own runs. Checking about 40 links takes roughly 4 seconds and costs about $0.0001 in platform usage.

### Why this checker?

- **Fewer false positives.** Links that are blocked by bot protection (LinkedIn's 999, many 403s, 429 rate limits, Cloudflare challenges) are reported as `blocked`, not `broken`. They usually work in a browser, so they should not clutter your fix list. You can switch this off.
- **HEAD first, GET to confirm.** Many servers answer HEAD requests wrongly. Before a link is reported as broken, it is checked again with GET.
- **Full redirect chains.** Every hop is listed with its status code (301/302/307/308). Loops, chains that are too long and invalid `Location` headers are detected.
- **Error types you can act on:** `http_404`, `http_500`, `dns`, `ssl`, `connection_refused`, `timeout`, `redirect_loop`, `too_many_redirects`, `blocked`.
- **Polite crawling.** Respects robots.txt, limits parallel requests per host and retries 429/503 responses with backoff.
- **Each URL is checked once.** A link that appears on 500 pages is requested one time. The report lists every page it was found on (up to 20 examples plus a total count).

### Use cases

- Monthly website QA and **SEO audit**: broken internal links, links to removed pages, redirect chains that waste crawl budget.
- **Website migration / relaunch**: check that every old URL redirects (preferably with a 301) to a working page.
- Agencies: run a scheduled broken-link report for each client site and send the dataset as CSV or Excel.
- Content teams: find dead outbound links in old blog posts.
- Affiliate and partner links: bulk-check that they still resolve.

### Input

| Field | Description |
|---|---|
| `startUrls` | Crawl mode: one start page per website. List mode: all URLs to check. |
| `mode` | `crawl` (default) crawls the site and checks every link found. `list` checks only the URLs you entered. |
| `outputMode` | `broken` (default in crawl mode): broken links and timeouts only. `issues`: also redirected and bot-blocked links. `all`: every checked link (default in list mode). |
| `maxPagesPerSite` | Number of HTML pages crawled per website (default 200). |
| `maxDepth` | Number of clicks from the start page (default 10). |
| `maxLinksToCheck` | Hard cap on unique URLs per run (default 10,000). |
| `checkExternalLinks` | Also check links to other domains (default on). They are checked but never crawled. |
| `checkResources` | Also check images, scripts, CSS, icons and media (default off). |
| `includePatterns` / `excludePatterns` | Limit the crawl. A plain string means "contains"; `*` wildcards and `re:` regular expressions also work. |
| `respectRobotsTxt` | Skip pages that robots.txt disallows (default on). |
| `treatBlockedAsBroken` | Report 401/403/429/999 as broken instead of blocked. |
| `concurrency`, `maxConcurrencyPerHost`, `timeoutSecs`, `maxRedirects` | Tuning options. |

Example: audit a site and save every problem.

```json
{
  "startUrls": [{ "url": "https://www.example.com/" }],
  "maxPagesPerSite": 500,
  "outputMode": "issues",
  "checkResources": true,
  "excludePatterns": ["/wp-admin/", "?replytocom="]
}
```

Example: bulk redirect check after a migration.

```json
{
  "mode": "list",
  "startUrls": [
    { "url": "http://example.com/old-page" },
    { "url": "https://example.com/blog/2019/post" }
  ]
}
```

### Output

One row per checked URL. This is real output from a run on scrapethissite.com:

```json
{
  "url": "https://www.scrapethissite.com/pages/advanced/?gotcha=csrf",
  "result": "broken",
  "statusCode": 400,
  "statusText": "Bad Request",
  "errorType": "http_400",
  "error": null,
  "finalUrl": "https://www.scrapethissite.com/pages/advanced/?gotcha=csrf",
  "redirectCount": 0,
  "redirectChain": [],
  "permanentRedirect": null,
  "isInternal": true,
  "linkType": "page",
  "contentType": "text/html",
  "responseTimeMs": 212,
  "foundOnCount": 1,
  "foundOn": [
    { "pageUrl": "https://www.scrapethissite.com/pages/advanced/", "anchorText": "CSRF & Hidden Values", "tag": "a" }
  ],
  "website": "https://www.scrapethissite.com/",
  "checkedAt": "2026-11-02T23:07:37+00:00"
}
```

Real results from list mode:

| URL | result | status | errorType | redirects |
|---|---|---|---|---|
| https://httpbin.org/redirect/3 | redirect | 200 | | 3 (302 → 302 → 302) |
| http://github.com/ | redirect | 200 | | 1 (to https) |
| https://httpbin.org/status/410 | broken | 410 | http_410 | 0 |
| https://nonexistent-domain-xyz-12345.de/ | broken | | dns | 0 |
| https://expired.badssl.com/ | broken | | ssl | 0 |
| https://www.linkedin.com/company/apify | blocked | 403 | blocked | 0 |

`result` is one of:

- `ok`: 2xx with no redirect
- `redirect`: 2xx after one or more redirects
- `broken`
- `timeout`
- `blocked`

The dataset has two views: **Overview** and **Redirect chains**. A `SUMMARY` record in the key-value store gives per-website totals: pages crawled, links found, broken, redirected, blocked, and how many were skipped by robots.txt, patterns or limits.

Export the results as CSV, Excel or JSON, or connect them to Google Sheets, Slack, Zapier or Make with Apify integrations. To get a weekly broken-link report, schedule the Actor.

### Tips

- Large sites: raise `maxPagesPerSite` and `maxLinksToCheck`, and keep `concurrency` moderate (10–20) to stay polite.
- Your own staging site behind basic auth or a firewall cannot be checked from the cloud.
- `blocked` links are usually fine. Open a few by hand to be sure.
- Only `http(s)` links are checked. `mailto:`, `tel:`, `javascript:` and `#anchors` are skipped.

### FAQ

**Is it legal?** The Actor only requests public URLs, the same way a browser or a search-engine bot does. It respects robots.txt for crawling. Check websites you own or have permission to audit.

**Does it render JavaScript?** No. It reads the HTML that the server returns. That is fast and cheap and covers most sites. Links that are injected only by client-side JavaScript are not discovered.

***

### Deutsch: Broken-Link-Checker & Redirect-Prüfer

Findet alle **defekten Links einer Website**: 404, 410, Serverfehler, nicht mehr existierende Domains, abgelaufene SSL-Zertifikate, Weiterleitungsschleifen und Timeouts. Zu jedem Link werden die **Seite, auf der er vorkommt, und der Linktext** gespeichert. Im Listenmodus prüft der Actor beliebig viele URLs auf Statuscode und **komplette Weiterleitungskette**, zum Beispiel nach einem Relaunch oder einer Domain-Migration.

- Von Bot-Schutz blockierte Links (LinkedIn 999, 403, Cloudflare) werden als `blocked` markiert und nicht fälschlich als defekt.
- Jede URL wird nur einmal geprüft, auch wenn sie auf vielen Seiten vorkommt.
- robots.txt wird beachtet; Anfragen pro Host sind begrenzt.
- Export als CSV/Excel. Per Zeitplan auch als wöchentlicher Linkreport, zum Beispiel für Agenturkunden.

**Kostenlos nutzbar.** Es fallen nur die Apify-Plattformkosten des eigenen Laufs an.

# Actor input Schema

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

Crawl mode: the start page(s) of the website(s) to audit (one entry per site). List mode: every URL whose status and redirect chain you want to check.

## `mode` (type: `string`):

'crawl' = crawl the website and check every link found. 'list' = only check the URLs you entered (bulk status code & redirect checker).

## `outputMode` (type: `string`):

'broken' = only broken links and timeouts. 'issues' = broken + redirected + bot-blocked links (SEO audit). 'all' = every checked link. List mode defaults to 'all'.

## `maxPagesPerSite` (type: `integer`):

How many HTML pages of each website are crawled for links. Links on these pages are all checked.

## `maxDepth` (type: `integer`):

How many clicks away from the start page the crawler goes (0 = only the start page).

## `maxLinksToCheck` (type: `integer`):

Hard cap on unique URLs checked in this run (pages + links + resources).

## `checkExternalLinks` (type: `boolean`):

Also check links pointing to other domains (they are checked, never crawled).

## `checkResources` (type: `boolean`):

Also check <img>, <script>, stylesheet, icon and media URLs, not only <a href> links.

## `includePatterns` (type: `array`):

Optional. Only pages matching one of these patterns are crawled (links on them are still all checked). Plain text = 'contains', wildcards like */blog/*, or 're:' + regular expression.

## `excludePatterns` (type: `array`):

Optional. Internal URLs matching one of these patterns are skipped, e.g. /wp-admin/, ?replytocom=, re:\\?page=\d+

## `respectRobotsTxt` (type: `boolean`):

Skip internal pages disallowed by the site's robots.txt.

## `treatBlockedAsBroken` (type: `boolean`):

By default 401/403/429/999 and Cloudflare challenges are reported as 'blocked' (the link usually works in a browser). Enable to report them as broken.

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

Total number of requests in flight.

## `maxConcurrencyPerHost` (type: `integer`):

Politeness limit per domain.

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

How long to wait for one server response before reporting a timeout.

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

Redirect chains longer than this are reported as broken (too_many_redirects).

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.scrapethissite.com/"
    }
  ],
  "mode": "crawl",
  "maxPagesPerSite": 200,
  "maxDepth": 10,
  "maxLinksToCheck": 10000,
  "checkExternalLinks": true,
  "checkResources": false,
  "respectRobotsTxt": true,
  "treatBlockedAsBroken": false,
  "concurrency": 10,
  "maxConcurrencyPerHost": 4,
  "timeoutSecs": 20,
  "maxRedirects": 10
}
```

# Actor output Schema

## `items` (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 = {
    "startUrls": [
        {
            "url": "https://www.scrapethissite.com/"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("paperlinenstudio/broken-link-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": "https://www.scrapethissite.com/" }] }

# Run the Actor and wait for it to finish
run = client.actor("paperlinenstudio/broken-link-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": "https://www.scrapethissite.com/"
    }
  ]
}' |
apify call paperlinenstudio/broken-link-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,paperlinenstudio/broken-link-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/OHUMozh3s7GdO5Q8w/builds/GSNzW6JhPYba7GMpb/openapi.json
