# Broken Link Checker – Find 404 & Dead Links on Any Site (`chris_elder/broken-link-checker`) Actor

Crawl starting URLs and check every internal and external link plus images. Reports broken (404), dead hosts, 5xx, redirect chains, with found-on page and anchor text. $0.50 per 1,000 links checked; failed checks are free.

- **URL**: https://apify.com/chris_elder/broken-link-checker.md
- **Developed by:** [Chris Elder](https://apify.com/chris_elder) (community)
- **Categories:** SEO tools, Developer tools
- **Stats:** 2 total users, 1 monthly users, 60.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.30 / 1,000 link 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

## Broken Link Checker – find 404s, dead links & redirect chains on any website

**Broken Link Checker** crawls your website and checks **every internal link, external link and image** it finds. You get a clean report of **broken links (404, 410, 5xx)**, **dead domains**, **redirect chains** and unreachable URLs – each with **the pages that link to it and the anchor text**, so you know exactly what to fix and where.

No browser, no proxies, no setup: paste your homepage and click Start. Ideal for **technical SEO audits, site migrations, content QA and monitoring**.

### Why this link checker?

- ✅ **Checks every unique link once** – even if it appears on 500 pages – and lists up to 20 (configurable) source pages with anchor text
- 🔁 **Full redirect chains** (`301 → 302 → 200`) so you can update links to their final URL after a migration
- 🛡️ **No false alarms from bot-protected sites** – LinkedIn-style 999, 403 and 429 answers from external sites are reported as `blocked`, not `broken`
- 🧠 **Smart HEAD → GET fallback** – servers that mis-handle HEAD requests are re-checked with GET before a link is called broken
- 🖼️ Optional checks for **images**, **scripts, stylesheets and iframes**
- 🗺️ **Finds orphan pages** via your XML sitemap (optional)
- 🧾 `RUN_SUMMARY` with totals by status – perfect for dashboards and alerts
- 🐢 **Polite by default** – 500 ms between requests to the same host, honest User-Agent, robots.txt respected, retries with back-off

### What you get

| Status | Meaning |
|---|---|
| `broken` | 4xx/5xx response, dead domain (DNS not found), refused connection, invalid URL or redirect loop |
| `redirect` | Works, but only after one or more redirects (see `redirectChain`, `finalUrl`) |
| `blocked` | An external site refused our checker (401/403/429/999) – usually fine for real visitors; check by hand |
| `error` | No answer (timeout, network error) after a retry |
| `skipped` | robots.txt disallows checking it |
| `ok` | 2xx response (only in output mode *All links*) |

#### Output example

```json
{
  "url": "https://www.crawlee.dev/python/docs/examples/playwright-crawler-with-fingeprint-generator",
  "status": "broken",
  "statusCode": 404,
  "finalUrl": "https://www.crawlee.dev/python/docs/examples/playwright-crawler-with-fingeprint-generator",
  "redirectChain": [],
  "linkType": "link",
  "isExternal": false,
  "foundOnCount": 1,
  "foundOn": [
    { "page": "https://crawlee.dev/blog/crawlee-for-python-v1", "text": "Playwright crawler with fingerprint generator" }
  ],
  "responseTimeMs": 87,
  "charged": true
}
```

(Real results from testing on a well-known documentation site: a typo 404, plus three links to non-existent `www.en.wikipedia.org`-style hosts reported as dead domains.)

📊 **Want these results in Google Sheets?** Send them straight to a Sheet with [Google Sheets Sync](https://apify.com/chris_elder/google-sheets-sync) – run it after this Actor, or add it on this Actor's **Integrations** tab so every run exports automatically.

### How much does it cost to check links?

**$0.50 per 1,000 links checked** ($0.0005 per unique link) + $0.0005 per run – **half the price** of the most-used broken link checker on the Store. Higher Apify plans get automatic discounts (Silver $0.0004, Gold+ $0.0003).

| Site size | Typical unique links | Price |
|---|---|---|
| Small business site (50 pages) | ~400 | ~$0.20 |
| Blog / docs (500 pages) | ~3,000 | ~$1.50 |
| Large site (5,000 pages) | ~20,000 | ~$10 |

You pay for each unique link that gets a definitive answer (OK, redirect or broken). **Unreachable, bot-blocked and robots.txt-skipped links are free**, and links found many times are charged once. Use *Max links to check* or Apify's *Max cost per run* to cap the price.

### How to use it

1. Click **Try for free** and enter your homepage.
2. Set **Max pages to crawl** (default 200) and choose what to output: *Problems* (default), *Broken links only* or *All links*.
3. Optionally exclude sections (`/tag/`), skip noisy links (`linkedin\.com`) or turn on sitemap discovery.
4. Click **Start** and download the report as CSV, Excel, JSON – or send it to Google Sheets, Slack or email via Apify integrations.

#### Input example

```json
{
  "startUrls": [{ "url": "https://www.example.com" }],
  "maxPages": 500,
  "outputMode": "broken",
  "checkExternalLinks": true,
  "checkImages": true,
  "excludeLinkPatterns": ["linkedin\\.com"]
}
```

### Use cases

- **SEO audits** – broken internal links waste crawl budget and link equity; fix them first
- **Website migrations & redesigns** – catch every redirect chain and 404 after the switch
- **Content QA** – find dead outbound references in blog posts and documentation
- **Monitoring** – schedule a weekly run and get alerted when new broken links appear
- **Document libraries** – check that the PDFs your site links to still load, then turn the live ones into searchable Markdown with our [PDF to Markdown converter](https://apify.com/chris_elder/pdf-to-markdown)
- Combine with our **Sitemap URL Extractor**, **On-Page SEO Audit** and **Bulk Lighthouse Auditor** for a complete technical SEO toolkit

### Is it OK to run a link checker?

Checking links is what browsers and search engines do every day. This Actor crawls only the site you give it, makes **one lightweight request per unique link** to other sites, respects robots.txt, spaces requests to each host and identifies itself honestly. It collects no personal data. Run it on sites you own or are authorised to audit.

### FAQ

**Does it render JavaScript?** It reads the HTML your server returns, which is what search engines index first. Links injected only by client-side JavaScript are not seen.

**Why is a link "blocked" and not "broken"?** Some sites (LinkedIn, some CDNs) refuse automated requests while working fine in a browser. We flag those separately so your broken list stays accurate.

**Can I use it via API, Make, Zapier or n8n?** Yes, like every Apify Actor.

**Found a wrong result?** Open an issue with the link – we respond quickly.

### Limits and memory

For large crawls (`maxPages` above ~50 or `maxLinksToCheck` above ~500), run at **2048 MB** (the Store default). Stress inputs at 512 MB can run out of memory. Keep `maxPages` and `maxLinksToCheck` modest on the Free plan; failures from oversized inputs are not charged as successful link checks.

### Need the data done for you?

If you'd rather not set up runs yourself, the UK company behind these tools (GPT Shop Ltd) offers a fixed-price done-for-you scrape. Tell us the website and the fields you need, and you get a clean CSV or Google Sheet within 48 hours of the confirmed brief, up to 5,000 rows, for £149 or $179. If we can't get the data, you're refunded in full. [See the done-for-you scraper service](https://tools.gptshopltd.com/done-for-you-scraper/?utm_source=apify\&utm_medium=readme\&utm_campaign=broken-link-checker)

### More tools by Chris Elder

[Sitemap URL Extractor](https://apify.com/chris_elder/sitemap-url-extractor) · [PDF to Markdown](https://apify.com/chris_elder/pdf-to-markdown) · [Bulk File Downloader](https://apify.com/chris_elder/bulk-file-downloader) · [Dataset Transformer](https://apify.com/chris_elder/dataset-transformer) · [all tools](https://apify.com/chris_elder)

# Actor input Schema

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

Homepage or any page of the site. The checker crawls internal pages from here and checks every link it finds. If left empty, a quick demo check of https://example.com (1 page) runs.

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

How many pages of your site to crawl for links (all links on those pages are checked).

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

Upper limit of unique links checked per run. You pay per link checked, so this caps your cost.

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

'problems' = broken, redirected, blocked and unreachable links. 'broken' = only broken links (4xx/5xx, dead domains). 'all' = every checked link.

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

Also check links to other websites (one request each, never crawled).

## `checkImages` (type: `boolean`):

Also check <img src> URLs.

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

Also check <script src>, stylesheets and iframes.

## `useSitemap` (type: `boolean`):

Add pages listed in the site's XML sitemap (found via robots.txt) – finds orphan pages that no menu links to.

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

How many clicks from the start URL to crawl.

## `includeUrlPatterns` (type: `array`):

Restrict which pages of your site are crawled, e.g. /docs/

## `excludeUrlPatterns` (type: `array`):

Pages to skip when crawling, e.g. /tag/ or ?page=

## `excludeLinkPatterns` (type: `array`):

Links to ignore entirely, e.g. linkedin.com or /cdn-cgi/

## `maxSourcesPerLink` (type: `integer`):

How many 'found on' pages (with anchor text) to list for each link.

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

Parallel requests across all hosts. Requests to the same host are also spaced by the delay below.

## `requestDelayMs` (type: `integer`):

Keeps load on your site and on linked sites negligible.

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

Links that don't answer in time are retried once, then reported as unreachable (not charged).

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

Don't crawl or check URLs that robots.txt disallows (they are listed as 'skipped', free).

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://example.com"
    }
  ],
  "maxPages": 1,
  "maxLinksToCheck": 50,
  "outputMode": "all",
  "checkExternalLinks": true,
  "checkImages": true,
  "checkResources": false,
  "useSitemap": false,
  "maxDepth": 10,
  "includeUrlPatterns": [],
  "excludeUrlPatterns": [],
  "excludeLinkPatterns": [],
  "maxSourcesPerLink": 20,
  "maxConcurrency": 5,
  "requestDelayMs": 200,
  "timeoutSecs": 20,
  "respectRobotsTxt": true
}
```

# Actor output Schema

## `overview` (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://example.com"
        }
    ],
    "maxPages": 1,
    "maxLinksToCheck": 50,
    "outputMode": "all",
    "checkExternalLinks": false,
    "checkImages": true,
    "requestDelayMs": 0
};

// Run the Actor and wait for it to finish
const run = await client.actor("chris_elder/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://example.com" }],
    "maxPages": 1,
    "maxLinksToCheck": 50,
    "outputMode": "all",
    "checkExternalLinks": False,
    "checkImages": True,
    "requestDelayMs": 0,
}

# Run the Actor and wait for it to finish
run = client.actor("chris_elder/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://example.com"
    }
  ],
  "maxPages": 1,
  "maxLinksToCheck": 50,
  "outputMode": "all",
  "checkExternalLinks": false,
  "checkImages": true,
  "requestDelayMs": 0
}' |
apify call chris_elder/broken-link-checker --silent --output-dataset

```

## MCP server setup

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