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

Broken link checker and 404 checker: crawls your website, checks every link, image and (optionally) script once, and lists dead links with the page and anchor text where each appears, plus redirect chains and DNS/SSL/timeout errors. $0.30 per 1,000 links checked.

- **URL**: https://apify.com/gazidev/broken-link-checker.md
- **Developed by:** [Cemal Atakli](https://apify.com/gazidev) (community)
- **Categories:** SEO tools, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.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.

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 on Any Website

**Broken Link Checker** crawls your website, checks every link it finds, and gives you a list of **dead links, 404 pages, broken images and failing redirects**. For each one it shows **the page and anchor text where the link appears**, so you know exactly what to fix. It uses plain HTTP and no browser, so it is fast and cheap: **$0.30 per 1,000 links checked**.

### What it does

- **Crawls your site** from one or more start URLs or a `sitemap.xml`. It stays on your domain, goes breadth-first, and you can limit it with `maxPages`, `maxDepth` and include/exclude globs.
- **Extracts every link:** `<a href>`, `<area>`, images (`<img src>`, `srcset`, `<picture>`), and optionally scripts, stylesheets, icons, iframes and video/audio sources.
- **Checks each unique URL once.** It sends HEAD first and falls back to GET when a server rejects HEAD. Redirects are followed by hand, so **every hop of the redirect chain is recorded**.
- **Finds every kind of broken link:** 4xx (404, 410, 403…), 5xx, **DNS errors** (dead domains), **timeouts**, **SSL/certificate errors**, connection refused/reset, **redirect loops**, too many redirects and invalid redirects.
- **Shows where to fix it:** `foundOn` lists each page that contains the link, with its anchor text (or image alt text) and HTML element.
- **Flags likely false positives.** External sites that block bots (403, 429, LinkedIn's 999) are marked `possibleFalsePositive` and are not mixed in with real dead links.
- **Writes a SUMMARY** to the key-value store: pages crawled, links checked, broken counts by status code and by link type, and the top 20 pages with the most broken links.
- **Is polite:** it respects robots.txt (Crawl-delay included), throttles requests per host, and sends an honest User-Agent with a contact address.

### Use cases

- **Website audit before launch or after a migration:** catch 404s, broken images and redirect chains.
- **Scheduled monitoring:** run it weekly with `onlyBroken: true` and get an alert or webhook only when new dead links appear.
- **SEO cleanup:** broken internal links waste crawl budget and link equity. Fix them using the "Where to fix" view.
- **Content teams and agencies:** find dead outbound links in blogs, docs and knowledge bases, and in client sites.
- **Sitemap health:** feed in `sitemap.xml` and see which listed URLs are broken.

### Input example

```json
{
  "startUrls": [{ "url": "https://www.example.com/" }],
  "maxPages": 500,
  "maxDepth": 20,
  "checkExternalLinks": true,
  "checkImages": true,
  "checkScriptsAndStyles": false,
  "excludeGlobs": [{ "glob": "https://www.example.com/cart/**" }],
  "onlyBroken": true,
  "concurrency": 5,
  "timeoutSecs": 15,
  "respectRobotsTxt": true
}
```

If you leave the input empty, the Actor runs a 45-second demo on `crawler-test.com`, a public crawler test site full of intentionally broken links.

| Field | Default | Meaning |
|---|---|---|
| `startUrls` | – | Website home page(s) or sitemap URL(s) |
| `maxPages` | 100 | HTML pages scanned for links, per site |
| `maxDepth` | 20 | Clicks from the start page (0 = start page only) |
| `checkExternalLinks` | true | Also check links to other domains (never crawled) |
| `checkImages` | true | Check `<img>`, `srcset`, `<picture>` |
| `checkScriptsAndStyles` | false | Check scripts, CSS, icons, iframes, media |
| `onlyBroken` | true | Output only broken links, or every checked link |
| `includeGlobs` / `excludeGlobs` | – | Limit which pages are crawled / skip URLs entirely |
| `maxLinksToCheck` | 10,000 | Cost cap: unique links checked per run |
| `useSitemap` | false | Also crawl pages listed in robots.txt sitemaps |
| `respectRobotsTxt` | true | Obey robots.txt and Crawl-delay |
| `concurrency` | 5 | Parallel requests to your site |
| `timeoutSecs` | 15 | A link slower than this is reported as `TIMEOUT` |

### Output example

One dataset row per broken link (or per checked link when `onlyBroken` is off):

```json
{
  "url": "https://crawler-test.com/redirects/invalid_redirect",
  "isBroken": true,
  "status": 404,
  "statusText": "Not Found",
  "errorType": "HTTP_4XX",
  "error": "HTTP 404 Not Found",
  "possibleFalsePositive": false,
  "redirectChain": [{ "url": "https://crawler-test.com/redirects/invalid_redirect", "status": 303 }],
  "redirectCount": 1,
  "finalUrl": "https://crawler-test.com/images/foo.jpg",
  "isExternal": false,
  "linkType": "page",
  "foundOnCount": 1,
  "foundOn": [{ "pageUrl": "https://crawler-test.com/", "anchorText": "Redirect 303 To 404", "element": "a" }],
  "firstSeenAt": "2026-10-11T12:00:48Z",
  "checkedAt": "2026-10-11T12:00:59Z",
  "responseTimeMs": 919,
  "method": "HEAD"
}
```

`errorType` is one of `HTTP_4XX`, `HTTP_5XX`, `DNS_ERROR`, `TIMEOUT`, `SSL_ERROR`, `CONNECTION_REFUSED`, `CONNECTION_RESET`, `CONNECTION_ERROR`, `PROTOCOL_ERROR`, `REDIRECT_LOOP`, `TOO_MANY_REDIRECTS`, `BAD_REDIRECT`.

The **SUMMARY** record in the key-value store looks like this:

```json
{
  "pagesCrawled": 10, "linksChecked": 394, "brokenLinks": 72, "brokenConfirmed": 70, "possibleFalsePositives": 2,
  "brokenInternal": 70, "brokenExternal": 2,
  "brokenByStatus": { "404": 8, "403": 3, "DNS_ERROR": 2, "REDIRECT_LOOP": 2, "TIMEOUT": 2, "500": 2 },
  "brokenByLinkType": { "page": 72 },
  "topPagesWithBrokenLinks": [{ "pageUrl": "https://crawler-test.com/", "brokenLinks": 72 }],
  "notChecked": { "robotsTxtDisallowed": 17 }
}
```

The Output tab has three views: **Broken links**, **Where to fix** (one row per page + anchor text, ready to hand to an editor) and **Redirect chains**.

### Pricing

Pay per event: **$0.30 per 1,000 unique links checked**, plus the Apify start fee of $0.00005. Crawled pages count as links. Duplicates, links skipped by robots.txt or your filters, and the summary are free. Set a maximum charge on the run and the Actor stops cleanly at that limit, keeping all results collected so far.

| Actor | Users (30 days) | Price |
|---|---|---|
| **This Actor** | new | **$0.30 per 1,000 links checked** |
| khadinakbar/broken-link-checker | 31 | $1.00 per 1,000 links checked |
| parseforge/broken-link-checker | 7 (rating 2.7) | see Store |
| logiover broken link checker | 3 | see Store |
| santamaria-automations broken link checker | 2 | see Store |

Typical costs: a 100-page site with about 1,200 links costs **$0.36**. The empty-input demo (394 links) costs $0.12.

### FAQ

**Does it run JavaScript?** No. It reads the HTML the server returns, which covers most websites, including WordPress, Shopify, Webflow, docs sites and static sites. Links that only appear after client-side rendering (some single-page apps) are not seen.

**Why is a link that works in my browser marked 403?** Some sites (LinkedIn, npm, some CDNs) block automated requests. These links are flagged `possibleFalsePositive: true` with a note, sorted last, and counted separately in `SUMMARY.possibleFalsePositives`.

**Does it overload my server?** No. By default it sends at most 5 parallel requests to your site and honors robots.txt `Crawl-delay` (then 1 request at a time). External hosts get at most 2 parallel requests. Each URL is requested only once.

**Can I check only one page?** Yes. Set `maxDepth: 0`: the start page is scanned and all of its links are checked.

**Can I check a list of URLs without crawling?** Use our *Bulk URL Status Checker* for that. This Actor is built for finding links *inside* pages.

**What is the User-Agent?** `Mozilla/5.0 (compatible; BrokenLinkChecker/0.1; +https://apify.com/gazidev/broken-link-checker; iletisim.ortalamabiri@gmail.com)`. robots.txt rules for `BrokenLinkChecker` (or `*`) are applied.

**What if a run is huge?** `maxPages` and `maxLinksToCheck` cap the work, and the run's maximum charge caps the cost. When the run timeout is near, the Actor stops early and still saves everything it has.

### Use with AI agents / Apify MCP

This Actor works as a tool for AI agents through the [Apify MCP server](https://mcp.apify.com) (Claude, ChatGPT, Cursor, and so on). Ask your agent to "check https://example.com for broken links". It calls `gazidev/broken-link-checker` with `startUrls` and gets back compact JSON rows: `url`, `status`, `errorType` and `foundOn` with page and anchor text. The agent can then summarize them or open fix tickets. Use `maxPages` and `maxLinksToCheck` to keep agent runs small and predictable. The same input works from the Apify API, Python/JS clients, Make, Zapier and n8n.

# Actor input Schema

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

Home page (or any page) of the website to check, e.g. `https://www.example.com`. The crawler stays on the same host (www and non-www count as the same site). You can also give a sitemap URL (`https://www.example.com/sitemap.xml`): its pages are crawled first. `https://` is added if missing. Empty input runs a small demo on crawler-test.com.

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

How many HTML pages of each site are opened and scanned for links (breadth-first, so the most important pages come first). Links found on these pages are all checked, even when they point to pages beyond this limit.

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

How many clicks away from the start URL pages are crawled. `0` = only scan the start page(s) themselves (their links are still checked). Links on the deepest pages are checked but not followed.

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

Also check links that point to other websites (each external URL is requested once, max 2 parallel requests per external host). External pages are never crawled.

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

Check `<img src>`, `srcset`, `<picture>` sources and video posters for broken images.

## `checkScriptsAndStyles` (type: `boolean`):

Also check `<script src>`, stylesheets, icons/manifest, `<iframe src>` and `<video>/<audio>` sources.

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

On: the dataset contains only broken links (4xx, 5xx, DNS, timeout, SSL, redirect loop). Off: every checked link is listed (broken ones first), including redirects and links skipped because of robots.txt or limits.

## `includeGlobs` (type: `array`):

Optional. Only pages whose URL matches one of these globs are crawled for more links, e.g. `https://www.example.com/blog/**`. `*` matches within one path segment, `**` matches anything. Links found on crawled pages are still all checked.

## `excludeGlobs` (type: `array`):

Optional. URLs matching these globs are neither crawled nor checked, e.g. `https://www.example.com/cart/**` or `**/logout*`.

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

Safety limit for unique links checked in one run (pages and resources included). Each checked link is one charged event, so this also caps the cost.

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

Read the sitemaps listed in robots.txt (or /sitemap.xml) and crawl those pages too, up to `maxPages`. Broken URLs found in a sitemap are reported with the sitemap as their source.

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

Do not crawl or check internal URLs disallowed by robots.txt, and honor Crawl-delay (up to 10 s). Turn off only for a website you own.

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

How many requests are sent to your website at the same time. Keep it low to stay polite. Forced to 1 when robots.txt sets a Crawl-delay.

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

A link that does not answer within this time is reported as broken with error `TIMEOUT`.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://crawler-test.com/"
    }
  ],
  "maxPages": 10,
  "maxDepth": 1,
  "checkExternalLinks": true,
  "checkImages": true,
  "checkScriptsAndStyles": false,
  "onlyBroken": true,
  "maxLinksToCheck": 10000,
  "useSitemap": false,
  "respectRobotsTxt": true,
  "concurrency": 5,
  "timeoutSecs": 15
}
```

# Actor output Schema

## `broken` (type: `string`):

No description

## `whereToFix` (type: `string`):

No description

## `summary` (type: `string`):

No description

## `results` (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://crawler-test.com/"
        }
    ],
    "maxPages": 10,
    "maxDepth": 1
};

// Run the Actor and wait for it to finish
const run = await client.actor("gazidev/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://crawler-test.com/" }],
    "maxPages": 10,
    "maxDepth": 1,
}

# Run the Actor and wait for it to finish
run = client.actor("gazidev/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://crawler-test.com/"
    }
  ],
  "maxPages": 10,
  "maxDepth": 1
}' |
apify call gazidev/broken-link-checker --silent --output-dataset

```

## MCP server setup

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