# Broken Link Checker: Dead Links, Images & Redirects (`adhistory/broken-link-checker`) Actor

Crawl your website and check every link, image and script. Get a list of broken links (404, timeouts, dead domains) with the page each one is on. Billed per link checked.

- **URL**: https://apify.com/adhistory/broken-link-checker.md
- **Developed by:** [Brandon Ob](https://apify.com/adhistory) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 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, images and redirects on your website

This broken link checker crawls your website and checks every link, image, script and stylesheet it finds. You get each one back with its HTTP status and the page where it sits, so you can filter the 404s, dead domains and timeouts and send the list to whoever fixes the site. It works on any public site, without a browser and without a login. You are charged per link checked, and you can tick **Only return broken links** to pay only for the problems.

### What does Broken Link Checker do?

- **Crawls your site** from the start pages you give it, following links on the same website up to a page and depth limit.
- **Checks every link it finds**: clickable links plus image, script, stylesheet and icon files, inside and outside your site.
- Marks each link `ok`, `broken` (404, dead domain, timeout, connection error) or `blocked` (the server refused a bot with 401, 403 or 429).
- Records the **status code, error, redirect target and number of redirects, response time**, link text and up to 5 pages that contain the link.
- Checks each distinct link once per run, however many pages contain it.
- Writes a `SUMMARY` record: pages crawled, links checked, broken and blocked counts, pages that failed to load, and whether the link limit cut the run short.

### Use cases

- **Website owners and marketers:** find 404s and dead outbound links before they hurt visitors and search rankings.
- **SEO agencies:** run a link audit for a client and hand over a filtered CSV of broken links with the page each sits on.
- **Developers and QA:** check a site after a migration, redesign or URL change for broken internal links and missing images.
- **Content teams:** clean up old blog posts whose outbound references have died or now redirect.
- **Ongoing monitoring:** schedule a weekly run with **Only return broken links** and get just the new problems.

### How to use it

1. Open the Actor in Apify Console and add your website address. The Actor follows links on the same site from there.
2. Set **Max pages to crawl** and **Max links to check** to cover your site. Optionally turn off external links or image and script files.
3. Click **Start**.
4. Open the **Checked links** view and filter `result = broken`, or download the results.
5. For regular checks, save the input as a task and schedule it, or call the Actor from the API.

### Input

| Field | What it does |
|---|---|
| `startUrls` | The pages to start from. |
| `maxPages` | How many pages of your site to open and read for links (default 25). |
| `maxDepth` | How many links deep to follow from the start pages (default 3; 0 checks only the start pages). |
| `maxLinks` | Upper limit of distinct links checked in one run (default 500). |
| `checkExternal` | Also check links that leave your site; those pages are checked, not crawled (on by default). |
| `checkImagesAndScripts` | Also check image, script, stylesheet and icon files (on by default). |
| `onlyBroken` | Return only broken links; you are charged only for the rows returned. |
| `timeoutSecs` | How long to wait for each link before marking a timeout (default 15). |
| `concurrency` | Parallel checks (default 10). Lower it if your server rate-limits you. |

Example input:

```json
{
  "startUrls": ["https://books.toscrape.com/"],
  "maxPages": 25,
  "maxDepth": 3,
  "maxLinks": 500,
  "checkExternal": true,
  "checkImagesAndScripts": true,
  "onlyBroken": false
}
```

### Output

One dataset item per distinct link. Example row:

```json
{
  "url": "https://books.toscrape.com/catalogue/category/books/travel_2/index.html",
  "status": 200,
  "result": "ok",
  "error": null,
  "finalUrl": null,
  "redirects": 0,
  "responseMs": 184,
  "type": "link",
  "scope": "internal",
  "anchorText": "Travel",
  "foundOn": ["https://books.toscrape.com/"]
}
```

A broken link looks like `"status": 404, "result": "broken"`, or `"status": null, "result": "broken", "error": "timeout"` when the server did not answer. The default run (books.toscrape.com) crawled 25 pages and checked 500 links, none broken. The dataset can be downloaded as JSON, CSV, Excel, XML or HTML, or read through the API.

### Pricing

Pay per event: **$1.00 per 1,000 links checked**, plus a $0.00005 charge per Actor start. With **Only return broken links** on, you pay only for the broken rows returned. You can set a maximum cost per run and the Actor stops cleanly at it.

Discounts apply automatically on paid Apify plans: 10% off on Starter, 20% on Scale and 30% on Business.

Example: a 200-page site with about 3,000 distinct links costs about **$3.00** for a full report, or a few cents with only broken links returned.

### Integrations and API

Run it and get the results back in one call:

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

Python, with the `apify-client` package:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("adhistory/broken-link-checker").call(run_input={
    "startUrls": ["https://example.com/"],
    "maxPages": 100,
    "onlyBroken": True,
})
for link in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(link["status"], link["url"], link["foundOn"])
```

Like every Apify Actor, it also works with Make, Zapier and n8n, with webhooks and schedules, and as a tool for AI agents through the Apify MCP server.

### FAQ

**It reports a link as broken but it opens for me.** Some servers answer bots differently. Check `status` and `error`, and open an issue with the URL so I can look at it.

**What does `blocked` mean?** The server answered 401, 403 or 429, which usually means it refuses automated requests. The link may still work in a browser, so it is not counted as broken.

**Does it see links added by JavaScript?** No. It reads the HTML the server sends. Links added by JavaScript after the page loads (single-page apps) are not seen.

**Which links are skipped?** `mailto:`, `tel:` and `#anchor` links are skipped, and anchors within a page are not verified. Links to other websites are checked but not crawled.

**Did it check my whole site?** Only up to your page, depth and link limits. `SUMMARY.truncated` is `true` when links were left unchecked because of **Max links to check**.

**Does it respect robots.txt?** It does not read robots.txt. Run it only on sites you own or are allowed to check, and lower **Parallel checks** if your server is slow.

### Related tools

- [Website Screenshot API](https://apify.com/adhistory/website-screenshot-api): full-page screenshots of the pages you fix.
- [Google Ads Transparency Center Scraper](https://apify.com/adhistory/google-ads-history-actor): see the ads and landing pages competitors use.
- [Email List Cleaner & Validator](https://apify.com/adhistory/email-list-cleaner-validator-syntax-mx-disposable): remove bad addresses from a contact list.
- [Google Sheets Export & Import](https://apify.com/adhistory/google-sheets-export-actor): send the broken-link report straight into a Google Sheet.

Found a bug or need a feature? Open an issue on the Issues tab — I usually reply within a day.

# Actor input Schema

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

The pages to start from. The Actor follows links on the same website from here and checks every link it finds.

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

How many pages of your site to open and read for links.

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

How many links deep to follow from the start URLs. 0 checks only the start pages.

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

Upper limit of distinct links checked in one run. You are charged per link checked.

## `checkExternal` (type: `boolean`):

Also check links that leave your site. Those pages are checked, not crawled.

## `checkImagesAndScripts` (type: `boolean`):

Also check image, script, stylesheet and icon files, not only clickable links.

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

Skip working links in the results. You are charged only for the rows returned.

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

How long to wait for each link to respond before marking it as a timeout.

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

Lower this if your server rate-limits you.

## Actor input object example

```json
{
  "startUrls": [
    "https://books.toscrape.com/"
  ],
  "maxPages": 25,
  "maxDepth": 3,
  "maxLinks": 500,
  "checkExternal": true,
  "checkImagesAndScripts": true,
  "onlyBroken": false,
  "timeoutSecs": 15,
  "concurrency": 10
}
```

# Actor output Schema

## `links` (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": [
        "https://books.toscrape.com/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("adhistory/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://books.toscrape.com/"] }

# Run the Actor and wait for it to finish
run = client.actor("adhistory/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://books.toscrape.com/"
  ]
}' |
apify call adhistory/broken-link-checker --silent --output-dataset

```

## MCP server setup

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