# Broken Link Checker — Find Dead Links on Any Website (`nexgensignal/broken-link-checker`) Actor

Check every link on a website, its sitemap or a plain URL list: one row per link with status, broken / blocked / redirect result, redirect chain, link text and linking pages. Images and scripts too, or only the problems. Part of NexGen Signal — data tools and official-source data products

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

## Pricing

from $33.50 / 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 Dead Links on Any Website

Give one or more websites and get **every link on them checked**, one row per unique link: whether it works, is redirected, is broken (4xx or 5xx) or is unreachable, its status code and redirect chain, the link text, and up to 20 pages that link to it.

The actor reads your site's pages (staying on your site, within the click depth you set, with the sitemap's pages too if you want), collects every link, and checks each unique link once, whether it appears on one page or on a thousand. Links to other sites are checked too, and you can also check images, scripts, stylesheets, icons and embedded media. You can also hand it a plain list of links to check as they are. Turn on `onlyBroken` to receive only the problems.

Results are `ok`, `redirect`, `blocked` (the server answered 401, 403, 429 or 999 to an automated check; the page may work in a browser), `broken` (any other 4xx or 5xx) or `unreachable` (no answer). Slow links are flagged.

The unit of output is one unique link delivered. With `onlyBroken`, every link is still checked but only broken and unreachable ones (and redirects, if you ask) are delivered and billed.

### Who buys this and for what job

- **SEO and content teams** fixing 404s and redirect chains that waste crawl budget.
- **Web agencies** checking a client site before and after a launch or migration.
- **Documentation teams** finding dead references in docs and help centres.
- **Site owners** running a scheduled weekly link check.

Buyers usually search for this as **"broken link checker"**, **"dead link finder"** or **"404 checker"**.

### 📊 Sample Output

![Table of broken links: link, result, status code, link text and the page it was found on.](https://api.apify.com/v2/key-value-stores/IXCaMKjxSmUTLHhmq/records/broken-link-checker.png)

| link_url | result | status_code | anchor_text | first_found_on |
|---|---|---|---|---|
| https://www.bankdirectorsdesktop.org/ | unreachable | — | Bank Director's Desktop | https://www.federalreserve.gov/sitemap.htm |
| https://www.ffiec.gov/nicpubweb/nicweb/nichome.aspx | blocked | 403 | National Information Center | https://www.federalreserve.gov/sitemap.htm |
| https://www.federalreserve.gov/releases/z1/current/default.… | broken | 404 | Financial Accounts of the United States - Z.1 | https://www.federalreserve.gov/sitemap.htm |
| https://www.federalreserve.gov/releases/z1/current/ | blocked | 403 | Growth | https://www.federalreserve.gov/azindex.htm |
| https://www.federalreserve.gov/start-job-search.htm | broken | 404 | Job postings | https://www.federalreserve.gov/azindex.htm |

Rows from live run `9zVb837zf1R5Lcneo` on Apify (25 pages of federalreserve.gov with onlyBroken and includeRedirects on). Notice the `blocked` rows: those servers refused an automated check, and the `error` field says to confirm them in a browser.

### ✅ What you get

Every field of each record:

- `record_id`, `link_url` — the link (absolute, without its #fragment).
- `result` — ok, redirect, blocked, broken or unreachable.
- `slow` — true when the answer took longer than slowThresholdMs (default 5 s).
- `status_code`, `final_url`, `redirect_count`, `redirect_chain` — final status, where it ends up, and every redirect hop.
- `error` — why nothing answered (timeout, host does not resolve, redirect loop), or a note when a server refuses automated checks.
- `link_type`, `element` — internal or external; a, area, img, script, link, iframe, source, video, audio, embed.
- `first_found_on`, `anchor_text`, `nofollow` — first page it was seen on, its link text there, and rel=nofollow.
- `occurrences`, `found_on_pages` — how often it appears, and up to 20 pages that link to it with their link text.
- `content_type`, `response_ms`, `checked_at` — type and time of the answer, and when it was checked (UTC).

### ⚙️ Sample inputs

**Three pages of a site (the Store example)**

```json
{
  "startUrls": ["https://www.federalreserve.gov/"],
  "maxPages": 3,
  "onlyBroken": false,
  "maxLinks": 10
}
```

Every link, working or not, up to 10.

**Only the problems on a whole site**

```json
{
  "startUrls": ["https://www.federalreserve.gov/"],
  "maxPages": 500,
  "onlyBroken": true,
  "includeRedirects": true,
  "maxLinks": 10000
}
```

Broken, unreachable and redirected links only.

**Images, scripts and stylesheets too**

```json
{
  "startUrls": ["https://www.nasa.gov/"],
  "maxPages": 1,
  "checkResources": true,
  "checkExternalLinks": false,
  "maxLinks": 100
}
```

Checks the page's own files as well as its links.

**A plain list of links**

```json
{
  "linkUrls": ["https://www.federalreserve.gov/", "https://www.usa.gov/"],
  "maxLinks": 100
}
```

Each link is checked as it is; nothing is opened for more links.

Input fields:

| Field | Meaning |
|---|---|
| `startUrls` | start pages (up to 100); the same site is read for more links. |
| `linkUrls` | a plain list of links to check as they are (up to 50,000); can be combined with startUrls. |
| `useSitemaps` | also read the pages in each site's sitemap (default off). |
| `requestTimeoutSecs`, `slowThresholdMs` | how long to wait for an answer (default 25 s) and what counts as slow (default 5,000 ms). |
| `maxPages` | pages of your site opened and read for links (default 100). |
| `maxDepth` | clicks from the start page (default 10). |
| `checkExternalLinks` | check links to other sites (default on). |
| `checkResources` | also check images, scripts, stylesheets, icons, iframes, video and audio (default off). |
| `onlyBroken`, `includeRedirects` | deliver only broken and unreachable links (optionally redirects too). |
| `includeSubdomains`, `includeUrlPatterns`, `excludeUrlPatterns` | which pages are read for links. |
| `maxLinks` | stop after this many links are delivered (default 10,000). Each delivered link is billed once. |

### 🧾 JSON sample record

```json
{
  "record_id": "85f45a6f89591bb86b617ae4",
  "link_url": "https://www.federalreserve.gov/apps/reportingforms/home/review",
  "result": "redirect",
  "status_code": 200,
  "final_url": "https://www.federalreserve.gov/apps/reportingforms/review",
  "redirect_count": 1,
  "redirect_chain": [
    {
      "url": "https://www.federalreserve.gov/apps/reportingforms/home/review",
      "status": 301
    }
  ],
  "error": null,
  "slow": false,
  "link_type": "internal",
  "element": "a",
  "first_found_on": "https://www.federalreserve.gov/",
  "anchor_text": "Information Collections Under Review",
  "nofollow": false,
  "occurrences": 24,
  "found_on_pages": [
    {
      "page": "https://www.federalreserve.gov/",
      "anchor_text": "Information Collections Under Review",
      "element": "a"
    },
    {
      "page": "https://www.federalreserve.gov/default.htm",
      "anchor_text": "Information Collections Under Review",
      "element": "a"
    },
    {
      "page": "https://www.federalreserve.gov/feeds/feeds.htm",
      "anchor_text": "Information Collections Under Review",
      "element": "a"
    },
    {
      "page": "https://www.federalreserve.gov/recentpostings.htm",
      "anchor_text": "Information Collections Under Review",
      "element": "a"
    },
    {
      "page": "https://www.federalreserve.gov/subscribe.htm",
      "anchor_text": "Information Collections Under Review",
      "element": "a"
    },
    {
      "page": "https://www.federalreserve.gov/newsevents/calendar.htm",
      "anchor_text": "Information Collections Under Review",
      "element": "a"
    },
    {
      "page": "https://www.federalreserve.gov/publications.htm",
      "anchor_text": "Information Collections Under Review",
      "element": "a"
    },
    {
      "page": "https://www.federalreserve.gov/sitemap.htm",
      "anchor_text": "Information Collections Under Review",
      "element": "a"
    },
    {
      "page": "https://www.federalreserve.gov/azindex.htm",
      "anchor_text": "Information Collections Under Review",
      "element": "a"
    },
    {
      "page": "https://www.federalreserve.gov/careers.htm",
      "anchor_text": "Information Collections Under Review",
      "element": "a"
    },
    {
      "page": "https://www.federalreserve.gov/faqs.htm",
      "anchor_text": "Information Collections Under Review",
      "element": "a"
    },
    {
      "page": "https://www.federalreserve.gov/videos.htm",
      "anchor_text": "Information Collections Under Review",
      "element": "a"
    },
    {
      "page": "https://www.federalreserve.gov/aboutthefed/contact-us-topics.htm",
      "anchor_text": "Information Collections Under Review",
      "element": "a"
    },
    {
      "page": "https://www.federalreserve.gov/aboutthefed.htm",
      "anchor_text": "Information Collections Under Review",
      "element": "a"
    },
    {
      "page": "https://www.federalreserve.gov/aboutthefed/the-fed-explained.htm",
      "anchor_text": "Information Collections Under Review",
      "element": "a"
    },
    {
      "page": "https://www.federalreserve.gov/aboutthefed/bios/board/default.htm",
      "anchor_text": "Information Collections Under Review",
      "element": "a"
    },
    {
      "page": "https://www.federalreserve.gov/aboutthefed/advisorydefault.htm",
      "anchor_text": "Information Collections Under Review",
      "element": "a"
    },
    {
      "page": "https://www.federalreserve.gov/aboutthefed/directors/about.htm",
      "anchor_text": "Information Collections Under Review",
      "element": "a"
    },
    {
      "page": "https://www.federalreserve.gov/aboutthefed/federal-reserve-system.htm",
      "anchor_text": "Information Collections Under Review",
      "element": "a"
    },
    {
      "page": "https://www.federalreserve.gov/aboutthefed/fract.htm",
      "anchor_text": "Information Collections Under Review",
      "element": "a"
    }
  ],
  "content_type": "text/html",
  "response_ms": 84,
  "checked_at": "2026-10-18T00:07:33Z"
}
```

### 🔧 How it works

Pages of your site are read 4 at a time while links are checked 8 at a time, with at most 2 requests at once to any one host and its robots.txt crawl-delay respected. A page of your site that is read counts as its own check. Other links get a HEAD request; when that answers with an error, a GET confirms it, because some servers answer HEAD wrongly.

Every run reads each host's robots.txt first: a path it disallows is skipped, listed in the run receipt and never charged. Only public internet addresses are fetched (no private, loopback or cloud-metadata addresses, checked again on every redirect). No logins, no CAPTCHA solving, no proxies.

Rows are delivered when checking finishes, so each row lists every page that links to it. If the run nears its time limit it stops reading pages and delivers what it checked. The run receipt (`RUN_RECEIPT`) has the counts by result, the links that were skipped and why, the pages with the most broken links, and the delivered and charged counts.

### 💰 Pricing example

Per link delivered, no start fee, no monthly charge:

| Apify plan | Price per link |
|---|---|
| Free | $0.05 |
| Bronze | $0.045 |
| Silver | $0.04 |
| Gold, Platinum, Diamond | $0.0335 |

- 10 links: $0.50 on the Free plan, $0.34 on Gold.
- 1,000 links: $50.00 on the Free plan, $33.50 on Gold.
- 10,000 links: $500.00 on the Free plan, $335.00 on Gold.

**Coverage and size.** Proof runs on 17 Oct 2026: 25 pages of federalreserve.gov in about 90 seconds; 809 unique links checked, 6 broken or unreachable, 2 blocked and 95 redirected (103 rows delivered with onlyBroken and includeRedirects); 4 NASA sitemap pages, 46 links, in 4 seconds; a plain list of 4 links in 2 seconds. Pages are read as the server sends them; links added only by JavaScript are not seen.

### ⚖️ Legal & ToS

You choose the sites. Check sites you own or have the right to check; every link is requested once per run, politely.

Every run reads each host's robots.txt first: a path it disallows is skipped, listed in the run receipt and never charged. Only public internet addresses are fetched (no private, loopback or cloud-metadata addresses, checked again on every redirect). No logins, no CAPTCHA solving, no proxies.

### ❓ FAQ

**What does `blocked` mean?**
The server answered 401, 403, 429 or 999 to an automated check. Some servers do that even when the page works in a browser, so these are reported apart from broken links; open them in a browser to confirm.

**Are #anchors checked?**
No. The page is checked; the fragment after # is not.

**Is a link on 50 pages billed 50 times?**
No. Each unique link is one row, with up to 20 linking pages listed in it.

### 🆘 Troubleshooting

- **Too few links checked** — Raise maxPages and maxDepth; RUN_RECEIPT shows pages read and links left unchecked.
- **Links skipped by robots.txt** — The target site's robots.txt disallows them; they are listed in RUN_RECEIPT and not billed.
- **SOURCE UNAVAILABLE** — None of your start pages could be reached after retries. Nothing was charged.

### 🔗 Related Actors

- [Website Performance Audit](https://apify.com/nexgensignal/website-performance-audit-api) — Lighthouse scores and web vitals per page
- [Website Tech Stack Detector](https://apify.com/nexgensignal/website-tech-stack-api) — what a site is built with
- [Website Contact Scraper](https://apify.com/nexgensignal/bulk-website-contact-extractor) — contact details from website lists

🏢 **About NexGen Signal** — data tools and official-source data products. Browse the catalog: [apify.com/nexgensignal](https://apify.com/nexgensignal)

### ⭐ Found this useful?

If this actor saved you time, a rating on its Apify Store page helps other buyers find it.

Part of NexGen Signal — data tools and official-source data products.

# Actor input Schema

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

Start pages. Pages on the same site (www and non-www count as one) are read for more links. Up to 100. Leave empty to check only a plain list (linkUrls).

## `linkUrls` (type: `array`):

Links to check as they are, without opening any page for more links (up to 50,000). Can be combined with startUrls.

## `useSitemaps` (type: `boolean`):

For each website, also open the pages listed in its sitemap (from robots.txt, or /sitemap.xml), up to maxPages.

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

How many pages of your site are opened and read for links. Every link found on them is checked.

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

How many clicks from the start page to follow (0 = start pages only).

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

Off = only links within your site are checked.

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

Also check img, script, stylesheet, icon, iframe, video and audio sources.

## `onlyBroken` (type: `boolean`):

On = only broken and unreachable links are delivered and billed; every link is still checked.

## `includeRedirects` (type: `boolean`):

Also deliver links that work but go through redirects (useful to update old links).

## `includeSubdomains` (type: `boolean`):

Treat subdomains (e.g. blog.example.com) as the same site.

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

Regular expressions; when set, only matching pages of your site are read for links (links on them are all checked).

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

Regular expressions for pages not to read, e.g. /search or ?page=

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

A link that has not answered by then is retried once, then reported as unreachable.

## `slowThresholdMs` (type: `integer`):

Links slower than this get slow = true.

## `maxLinks` (type: `integer`):

Stop after this many links are delivered. Each delivered link is billed once, however many pages it appears on.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.federalreserve.gov/"
  ],
  "linkUrls": [],
  "useSitemaps": false,
  "maxPages": 3,
  "maxDepth": 10,
  "checkExternalLinks": true,
  "checkResources": false,
  "onlyBroken": false,
  "includeRedirects": false,
  "includeSubdomains": false,
  "includeUrlPatterns": [],
  "excludeUrlPatterns": [],
  "requestTimeoutSecs": 25,
  "slowThresholdMs": 5000,
  "maxLinks": 10
}
```

# Actor output Schema

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

The delivered (and billed) records.

## `receipt` (type: `string`):

Per-link outcome, robots result, delivered and charged counts.

# 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": [
        "https://www.federalreserve.gov/"
    ],
    "maxPages": 3,
    "onlyBroken": false,
    "maxLinks": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("nexgensignal/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": ["https://www.federalreserve.gov/"],
    "maxPages": 3,
    "onlyBroken": False,
    "maxLinks": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("nexgensignal/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": [
    "https://www.federalreserve.gov/"
  ],
  "maxPages": 3,
  "onlyBroken": false,
  "maxLinks": 10
}' |
apify call nexgensignal/broken-link-checker --silent --output-dataset

```

## MCP server setup

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