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

Crawl any website and find broken links: 404s, 5xx errors, dead domains, timeouts, broken images and redirect chains, with the exact page and anchor text where each bad link appears. Bulk sites, CSV export, scheduled checks.

- **URL**: https://apify.com/datawright-labs/broken-link-checker.md
- **Developed by:** [Datawright Labs](https://apify.com/datawright-labs) (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.40 / 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.
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

**Find every broken link on a website in one run.** Enter a domain, and this broken link checker crawls its pages, checks **every internal and external link** (and optionally every **image, script and stylesheet**), and gives you a clean list of **404s, 410s, 5xx server errors, dead domains (DNS failures), timeouts, TLS errors and redirect chains**, each with the **exact pages and anchor text where the bad link appears**, so you know what to fix and where.

No browser, no install, no desktop crawler licence: it's a fast HTTP link checker that runs in the cloud, handles **many websites per run**, exports to **CSV, Excel, JSON or Google Sheets**, and can run on a **schedule** to catch new dead links before your visitors (and Google) do.

### What does this broken link checker do?

- 🕷️ **Crawls your site** from any start URL (same host, `www.` ignored), up to your page and depth limits, respecting `robots.txt`.
- 🔗 **Checks every link it finds**: internal links, **outbound/external links**, and optionally **images, scripts and CSS** (broken image checker).
- 🚦 **Classifies each URL**: `broken` (404, 410, 5xx, DNS lookup failed, connection refused, timeout, bad TLS certificate, malformed URL), `blocked` (401/403/429/999: the site refused automated checks, verify by hand), `redirect` (works, but via 1+ redirects) or `ok`.
- 📍 **Shows where each broken link is**: `foundOn` lists the source pages with **anchor text**, plus a total `foundOnCount`, so you can fix the most-linked dead URLs first.
- 🔁 **Redirect audit**: final URL and hop count for every redirected link, to clean up redirect chains and update internal links.
- 🧾 A **SUMMARY** record with pages crawled per site, counts per status and the **pages with the most broken links**.
- ⚡ **Fast and light**: header-only checks, parallel requests with a per-host politeness limit, automatic retries for timeouts, 429s and 5xx.

### Use cases

- **SEO audits**: broken internal links waste crawl budget and leak link equity; dead outbound links hurt user trust.
- **Website migrations & redesigns**: verify every old URL still resolves or redirects after a relaunch.
- **Content maintenance**: find dead references in old blog posts, docs and knowledge bases.
- **Agencies**: run one job across all client sites every week and send the report to Sheets or Slack.
- **Link building / outreach**: find broken outbound links on other sites (broken link building).

### How to use it

1. Paste one or more websites into **Websites to check** (e.g. `https://example.com`).
2. Set **Max pages to crawl per site** (start with 50-200) and, optionally, turn on **Check images, scripts and stylesheets**.
3. Click **Start**. Open the **Broken & blocked links** view, or download CSV/Excel.

#### Input example

```json
{
  "startUrls": ["https://example.com", "https://blog.example.org"],
  "maxPagesPerSite": 500,
  "checkExternalLinks": true,
  "checkResources": true,
  "outputMode": "problemsAndRedirects",
  "excludePatterns": ["/tag/", "?replytocom="]
}
```

To check the links on **a single page** only, set `maxDepth` to `0`.

### Output example

```json
{
  "url": "https://example.com/old-pricing",
  "status": "broken",
  "httpStatus": 404,
  "error": null,
  "isInternal": true,
  "linkType": "page",
  "site": "example.com",
  "foundOnCount": 3,
  "foundOn": [
    { "pageUrl": "https://example.com/blog/launch", "anchorText": "see our pricing", "tag": "a" },
    { "pageUrl": "https://example.com/faq", "anchorText": "pricing page", "tag": "a" }
  ],
  "firstFoundOn": "https://example.com/blog/launch",
  "anchorText": "see our pricing",
  "finalUrl": null,
  "redirectCount": 0,
  "contentType": "text/html",
  "responseTimeMs": 182,
  "checkedAt": "2026-10-20T01:00:00.000Z"
}
```

Rows are sorted with broken links first, then blocked, then redirects, each by number of occurrences.

### How much does it cost to check a website for broken links?

You pay **$0.0005 per unique URL checked** ($0.50 per 1,000), whether it's a crawled page or a link target. Every URL is checked once per run even if it appears on hundreds of pages. Excluded URLs, `robots.txt`-disallowed URLs and `mailto:`/`tel:` links are free. No start fee.

| Job | Unique URLs checked (typical) | Cost |
|---|---|---|
| One page's links (`maxDepth: 0`) | ~50-150 | **~$0.03-0.08** |
| Small site, 50 pages | ~500 | **~$0.25** |
| 1,000 unique URLs | 1,000 | **$0.50** |
| Blog/docs site, 500 pages + images | ~5,000 | **~$2.50** |
| Weekly check of 10 small client sites | ~5,000/week | **~$10/month** |

- Apify's **free plan includes $5 of monthly credit**, i.e. about **10,000 link checks a month for free**.
- Paid Apify plans get automatic discounts: **Silver -10%**, **Gold and above -20%**.
- Set **Max pages to crawl per site** and **Maximum cost per run** to hard-cap spend; the Actor stops cleanly at the limit.

### Broken link checker vs. the alternatives

| | This Actor | Free online checkers | Desktop SEO crawlers | Site-audit SaaS |
|---|---|---|---|---|
| No install, runs in the cloud | ✅ | ✅ | ❌ | ✅ |
| Pages per scan | as many as you set | often capped (e.g. a few hundred) | large, limited by your PC / licence | by plan |
| Internal + external + image links | ✅ | partial | ✅ | ✅ |
| Source page + anchor text per broken link | ✅ | sometimes | ✅ | ✅ |
| Many sites per run, API, schedules, webhooks | ✅ | ❌ | ❌ | partial |
| CSV / Excel / JSON / Google Sheets | ✅ | rarely | ✅ | ✅ |
| Cost model | $0.50 per 1,000 URLs, no subscription | free with caps | annual licence | monthly subscription |

### Use it as an API (Python, JavaScript, cURL, MCP)

Every Apify Actor is also a REST API, so you can run this broken link checker from your own code, CI pipeline, a no-code tool or an AI agent.

#### Python

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("datawright-labs/broken-link-checker").call(run_input={"startUrls": ["https://example.com"], "maxPagesPerSite": 100})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["status"], item["url"], item["firstFoundOn"])
```

#### JavaScript / Node.js

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('datawright-labs/broken-link-checker').call({ startUrls: ['https://example.com'], maxPagesPerSite: 100 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

#### cURL (run and get results in one request)

```bash
curl -X POST "https://api.apify.com/v2/acts/datawright-labs~broken-link-checker/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"startUrls": ["https://example.com"], "maxPagesPerSite": 100}'
```

#### AI agents (MCP)

Add `https://mcp.apify.com/?tools=datawright-labs/broken-link-checker` to Claude, Cursor or any MCP client and the agent can run it as a tool.

Results can also go straight to **Google Sheets, Slack, Make, Zapier, n8n or a webhook** via Apify integrations, and you can run it on a **Schedule** (e.g. every Monday) to monitor for new broken links.

### FAQ

#### How do I find broken links on my website?

Enter your domain and run the Actor. It crawls your pages, tests every link, and lists each broken one with the page and anchor text where it appears. Download the list as CSV or Excel and fix the links in your CMS.

#### What counts as a broken link?

A link whose target returns HTTP 4xx (except 401/403/429, which are reported as `blocked`) or 5xx after retries, or that fails with a network error: DNS lookup failed (dead domain), connection refused/reset, timeout or an invalid TLS certificate. Malformed `href` values are reported too.

#### Why are some links "blocked" instead of broken?

Sites like LinkedIn (status 999), some CDNs and bot-protected pages refuse automated requests even though the page works in a browser. They are listed separately so you can verify them by hand instead of chasing false positives. Enabling Apify Proxy can reduce them.

#### Does it check images and other assets?

Yes, turn on **Check images, scripts and stylesheets** to also verify `<img>`, `<script>` and stylesheet URLs (broken image checker).

#### Does it render JavaScript?

No. It reads the HTML your server returns, like a search-engine crawler's first pass. Links that only exist after client-side rendering are not seen. Most CMS, blog, docs and e-commerce sites serve their links in the HTML.

#### Can I check only one page?

Yes. Set **Max crawl depth** to `0` to check all links on the start page(s) only.

#### Will it overload my server?

No. It sends at most **Parallel requests per host** (default 8) requests to one host at a time, uses header-only checks for link targets, and respects `robots.txt`.

#### Can I schedule it and get alerts?

Yes. Create an Apify Schedule (daily or weekly) and connect Slack, email, Google Sheets or a webhook integration to receive new results.

### More tools from Datawright Labs

- [Sitemap URL Extractor](https://apify.com/datawright-labs/sitemap-url-extractor): get every URL of a site from its sitemaps.
- [Bulk PageSpeed & Core Web Vitals Checker](https://apify.com/datawright-labs/pagespeed-auditor): audit PageSpeed / Lighthouse scores for many URLs.
- [Website Screenshot API](https://apify.com/datawright-labs/website-screenshot): bulk full-page screenshots and PDFs.
- [Google Autocomplete Keyword Scraper](https://apify.com/datawright-labs/autocomplete-keyword-scraper): long-tail keyword ideas from Google, YouTube and Bing.
- [Greenhouse, Lever & Ashby Jobs Scraper](https://apify.com/datawright-labs/ats-jobs-scraper): normalized job feeds from 5 ATS platforms.
- [App Store Top Charts & Rank Tracker](https://apify.com/datawright-labs/app-store-top-charts): daily top free/paid/grossing app rankings with rank changes.

Made by **Datawright Labs**: reliable, fairly priced web data tools. Questions or a missing feature? Open an issue on the Issues tab; we usually reply within a day.

# Actor input Schema

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

One website or page per line (e.g. https://example.com or example.com). The Actor crawls internal pages from each start URL and checks every link it finds.

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

How many internal HTML pages to open and scan for links per site. Links found on those pages are all checked, even links to pages beyond this limit (those are checked but not scanned). Tip: also set 'Maximum cost per run' in the run options.

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

How many clicks away from the start URL to crawl. 0 = scan only the start page itself (useful to check a single page's links).

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

Also check links pointing to other websites (outbound links). Turn off to check internal links only.

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

Also check <img src>, <script src> and stylesheet URLs, to catch broken images and missing assets.

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

'Problems only' saves broken and blocked links (smallest dataset). 'Problems + redirects' also lists redirected links (good for cleaning up redirect chains). 'Everything' saves every checked URL. You pay per URL checked either way.

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

Don't crawl or check URLs matching any of these patterns. Plain text = case-insensitive 'contains' (e.g. /tag/, ?replytocom=, /cdn-cgi/), \* = wildcard, or a regular expression between slashes.

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

Don't crawl or check internal URLs that the site's robots.txt disallows. Turn off only for your own sites.

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

For every broken link, list up to this many pages where it appears (with anchor text). The total count is always reported in foundOnCount.

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

Maximum requests in flight overall.

## `maxRequestsPerHost` (type: `integer`):

Maximum requests in flight to any single host, to stay polite and avoid rate limits.

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

A link that doesn't respond within this time (after 2 retries) is reported as broken with a timeout error.

## `proxyConfiguration` (type: `object`):

Optional. Use Apify Proxy if the site blocks datacenter IPs.

## Actor input object example

```json
{
  "startUrls": [
    "https://crawlee.dev"
  ],
  "maxPagesPerSite": 50,
  "maxDepth": 10,
  "checkExternalLinks": true,
  "checkResources": false,
  "outputMode": "problems",
  "excludePatterns": [],
  "respectRobotsTxt": true,
  "maxSourcesPerLink": 10,
  "maxConcurrency": 20,
  "maxRequestsPerHost": 8,
  "requestTimeoutSecs": 15,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `links` (type: `string`):

Broken, blocked and (optionally) redirected links with the pages and anchor text where they appear.

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

SUMMARY record: pages crawled per site, counts of broken/blocked/redirected/OK URLs and the pages with the most broken links.

# 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://crawlee.dev"
    ],
    "maxPagesPerSite": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("datawright-labs/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://crawlee.dev"],
    "maxPagesPerSite": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("datawright-labs/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://crawlee.dev"
  ],
  "maxPagesPerSite": 50
}' |
apify call datawright-labs/broken-link-checker --silent --output-dataset

```

## MCP server setup

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