# Broken Link Checker — Find Broken Links, Images & Redirects (`saliga/broken-link-checker`) Actor

Crawl a website and check every link and image. Each broken link comes with the pages it is on and its link text, so you know where to fix it. Internal and external links, redirects, robots.txt honoured.

- **URL**: https://apify.com/saliga/broken-link-checker.md
- **Developed by:** [Salih Küçükkayış](https://apify.com/saliga) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 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.

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 broken links on a website. The Actor crawls your site, checks **every link and image** it finds (internal and external), and lists each **broken link with the pages it is on and its link text**, so you know exactly where to fix it. 404s, 410s, server errors, dead domains and timeouts are errors; links a site refuses to automated checks (401, 403, 429, LinkedIn's 999) are listed as warnings. Optionally it also reports **redirects**, checks **stylesheets, scripts and embeds**, or outputs every link for a full inventory.

**$0.50 per 1,000 URLs checked.** URLs that robots.txt closes are free. No browser, no proxy: fast and cheap.

### Quick start

Press **Start** with the example input (a public test site with deliberately broken links, small limits):

```json
{
  "startUrls": [{"url": "https://crawler-test.com/"}],
  "maxPagesToCrawl": 5,
  "maxLinksToCheck": 150
}
```

For your own site, give its address and raise the limits:

```json
{
  "startUrls": [{"url": "https://www.example.com/"}],
  "maxPagesToCrawl": 2000,
  "reportRedirects": true
}
```

One row per problem link (a real run on the example input, trimmed):

```json
{
  "url": "https://crawler-test.com/redirects/redirect_to_404",
  "status": 404,
  "severity": "error",
  "problem": "Not found (404)",
  "linkType": "internal",
  "kind": "page",
  "foundOnCount": 1,
  "foundOn": [{"page": "https://crawler-test.com/", "text": "Redirect To 404 Http Status", "tag": "a"}],
  "firstFoundOn": "https://crawler-test.com/"
}
```

The same run found 8 problems in 143 URLs on 5 pages in under a minute: 404s, redirect loops ("Too many redirects") and a redirect that ends on a 404.

### What it does

- **Crawls your site**: follows links on the same site from the start pages (www. and the bare domain count as one site), up to *Max pages to crawl*. *Crawl only pages matching* and *Do not crawl pages matching* limit the crawl with regular expressions.
- **Checks every unique URL once**: crawled pages by their own request; every other link, image and file with a HEAD request. When HEAD fails, a GET confirms it (many servers answer HEAD badly), without downloading the file.
- **External links are checked, never crawled.**
- **Where to fix**: each problem lists the pages that link to it (up to *Pages listed per broken link*), the link text or image alt text, and the total count.
- **Severity**: `error` (404, 410, other 4xx, 5xx, DNS failure, timeout, connection error), `warning` (401, 403, 429, 999: often bot protection, check in a browser), `redirect` (with *Report redirects*), `ok` and `skipped` (with *Report all links*).
- **Summary** in the key-value store record `SUMMARY`: pages crawled, URLs checked, broken links, warnings, redirects, counts by HTTP status.

### Polite and safe

- robots.txt is honoured for every page crawled and every link checked. A URL that robots.txt closes is not requested and not charged.
- One request at a time per host, with a short pause; different hosts are checked in parallel.
- Private and internal addresses (localhost, 10.x, 192.168.x, cloud metadata) are blocked, including every redirect.
- Use it on your own sites, or sites you have permission to crawl.

### Input

| Field | Default | What it does |
|---|---|---|
| `startUrls` | | Websites or start pages |
| `maxPagesToCrawl` | 500 | Pages read for links |
| `maxLinksToCheck` | 10,000 | Cap on unique URLs checked (and on the price) |
| `checkExternalLinks` | true | Check links to other sites |
| `checkImages` | true | Check `<img>` and `srcset` |
| `checkAssets` | false | Also stylesheets, icons, scripts, iframes, video, audio |
| `reportRedirects` | false | List links that redirect, with their final URL |
| `reportAllLinks` | false | Output every checked URL, not only problems |
| `includePatterns`, `excludePatterns` | | Regular expressions limiting which pages are crawled |
| `maxSourcesPerLink` | 10 | Pages listed for each problem link |

### Output

One row per problem link: `url`, `status`, `severity`, `problem`, `finalUrl` (redirects), `linkType` (internal or external), `kind` (page, image or asset), `foundOnCount`, `foundOn` (page, text, tag), `firstFoundOn`, `contentType`. Errors come first, internal before external, most-linked first. Export as JSON, CSV or Excel, or connect the dataset to Slack or email with an Apify integration.

### What it costs

| Run | Price |
|---|---|
| A 200-page site with 3,000 unique links | $1.60 |
| 10,000 URLs | $5.00 |
| Weekly check of a 50-page site (about 800 URLs) | $0.40 a week |

You only pay for URLs checked. The run stops cleanly at your spending limit or shortly before its timeout, and still writes everything it found.

### Good to know

- Only links in the HTML are found. Links that JavaScript adds after the page loads are not seen (no browser).
- Some sites refuse automated requests (LinkedIn, some social networks, sites behind bot protection). They are listed as warnings, not errors, so you can decide.
- Checks are polite: about 4 requests a second per host, so a 2,000-page site takes 10 minutes or more. Raise the run timeout for big sites; the run stops before the timeout and keeps its results.
- To check a list of URLs without crawling, use [Bulk HTTP Requests](https://apify.com/saliga/bulk-http-requests) or the status check of [Sitemap Extractor](https://apify.com/saliga/sitemap-extractor).

### FAQ

**Can I get an alert when a new broken link appears?** Yes: schedule the Actor (for example weekly) and connect the dataset to email or Slack with an Apify integration.

**Can an AI agent use it?** Yes, through the Apify MCP server: "check example.com for broken links".

### More from this developer

- [Sitemap Extractor](https://apify.com/saliga/sitemap-extractor): every URL a website lists in its sitemaps, with lastmod, images and hreflang.
- [Tech Stack Detector](https://apify.com/saliga/tech-stack-detector): CMS, ecommerce platform, analytics and hosting of a list of websites.
- [Website Screenshot + Change Detection](https://apify.com/saliga/website-screenshot-change-detection): screenshots of many URLs with visual change detection between runs.
- [Bulk HTTP Requests](https://apify.com/saliga/bulk-http-requests): call any API once for every row of a CSV, JSON list or Apify dataset.
- [Keyword Search Volume](https://apify.com/saliga/keyword-search-volume): Google search volume, CPC and trends for a keyword list.

Code examples (curl, Python, Node.js, n8n) for this and the other Actors: [github.com/msalihk/apify-actor-examples](https://github.com/msalihk/apify-actor-examples).

Questions or a missing feature: open an issue on the Actor page.

# Actor input Schema

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

The crawl starts here and follows links on the same site (www. and the bare domain count as one site). example.com or https://example.com/blog.

## `maxPagesToCrawl` (type: `integer`):

Pages of your site that are read for links. Links on further pages are not found.

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

Cap on unique URLs checked in total (crawled pages count too). Also a cap on the price: $0.50 per 1,000.

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

Check links to other websites (they are checked, never crawled).

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

Check <img> sources and srcset.

## `checkAssets` (type: `boolean`):

Also check <link> stylesheets and icons, <script src>, <iframe>, <video>, <audio> and <source>.

## `reportRedirects` (type: `boolean`):

Also list links that work but redirect (worth updating for SEO), with their final URL.

## `reportAllLinks` (type: `boolean`):

Output every checked URL, not only problems. Useful for a full link inventory.

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

Regular expressions on the page URL, e.g. /blog/. Links on other pages are not followed (links to them are still checked).

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

Regular expressions, e.g. ?page= or /tag/.

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

How many pages (with link text) to list for each problem link. The total count is always given.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://crawler-test.com/"
    }
  ],
  "maxPagesToCrawl": 5,
  "maxLinksToCheck": 150,
  "checkExternalLinks": true,
  "checkImages": true,
  "checkAssets": false,
  "reportRedirects": false,
  "reportAllLinks": false,
  "maxSourcesPerLink": 10
}
```

# Actor output Schema

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

Broken links and warnings, with the pages they are on and their link text.

## `fullResults` (type: `string`):

Every field, including final URLs of redirects and content types.

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

Pages crawled, URLs checked, broken links, warnings, redirects and counts by HTTP status.

# 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/"
        }
    ],
    "maxPagesToCrawl": 5,
    "maxLinksToCheck": 150
};

// Run the Actor and wait for it to finish
const run = await client.actor("saliga/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/" }],
    "maxPagesToCrawl": 5,
    "maxLinksToCheck": 150,
}

# Run the Actor and wait for it to finish
run = client.actor("saliga/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/"
    }
  ],
  "maxPagesToCrawl": 5,
  "maxLinksToCheck": 150
}' |
apify call saliga/broken-link-checker --silent --output-dataset

```

## MCP server setup

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