# Broken Link Checker: Pages List, Images, Anchor Text (`variegated_binturong/broken-link-checker-pages`) Actor

Paste a list of pages and get every broken link and image on them, with status code, anchor text and internal or external flag. Redirecting links and bot-blocked links are listed apart, so you fix what is really broken.

- **URL**: https://apify.com/variegated_binturong/broken-link-checker-pages.md
- **Developed by:** [Luigi Fiano](https://apify.com/variegated_binturong) (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

$4.00 / 1,000 page 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: Pages List, Images, Anchor Text

Paste a list of pages and get every broken link on them. Each page is opened, every link and image on it is checked, and you get one row per page with the broken links, their status code, their anchor text and whether they are internal or external.

No crawl to configure and no surprise bills: the checker looks only at the pages you give it. Ideal when you already know which pages matter (top landing pages, blog posts, a client's money pages, the URLs of a sitemap export).

### What it is for

- **SEO audits**: find 404s and dead external links on the pages that bring traffic.
- **Content maintenance**: old blog posts pointing to sites that no longer exist.
- **After a migration**: see which links still go through redirects and should be updated to the final URL.
- **Monitoring**: schedule it weekly with `onlyProblems` and get only the pages that broke.

### What it checks

- Every `<a href>` on the page, resolved against the page URL and `<base>`. Same-page `#anchors`, `mailto:`, `tel:`, `javascript:` and links inside HTML comments are skipped.
- Images (`<img src>`), and optionally scripts and stylesheets.
- Each unique link is checked once per run, even when it appears on hundreds of pages.
- `HEAD` first, then `GET` when the server does not answer `HEAD` properly (405, 501 and the many servers that answer `HEAD` with 403 or 404).
- Redirects are followed hop by hop, with loop protection.
- Links are sorted into three lists:
  - **broken**: 4xx (except the bot-blocking ones below), 5xx, DNS errors, timeouts, refused connections, redirect loops, invalid URLs;
  - **blocked**: 401, 403, 429 and 999 (LinkedIn and others). These usually mean "this site blocks automated checks", not "the page is gone", so they are listed apart and do not mark the page as failed;
  - **redirects**: links that work but go through one or more redirects, with the final URL.
- Anchor text (or image alt text) and the `nofollow` / `sponsored` / `ugc` flag for every reported link, so you can find it on the page.

### Input

| Field | What it does | Default |
|---|---|---|
| `urls` | One page URL per line | required |
| `checkExternal` | Also check links to other domains | true |
| `includeImages` | Also check images | true |
| `includeAssets` | Also check scripts and stylesheets | false |
| `reportRedirects` | List links that redirect | true |
| `maxLinksPerPage` | Max unique links checked per page | 500 |
| `onlyProblems` | Output only pages with broken or blocked links | false |
| `concurrency`, `linkConcurrency`, `timeoutSecs`, `maxRetries` | Speed and resilience | 3, 10, 15, 1 |

Example:

```json
{ "urls": ["https://www.example.com/", "https://www.example.com/blog/old-post/"] }
```

### Output

One row per page:

```json
{
  "url": "https://www.example.com/blog/old-post/",
  "finalUrl": "https://www.example.com/blog/old-post/",
  "status": 200,
  "linksFound": 64,
  "linksChecked": 64,
  "brokenCount": 2,
  "blockedCount": 1,
  "redirectCount": 1,
  "broken": [
    { "url": "https://www.example.com/pricing-2019/", "type": "link", "text": "our old pricing", "internal": true, "nofollow": false, "status": 404, "error": null, "finalUrl": "https://www.example.com/pricing-2019/" },
    { "url": "https://some-closed-tool.io/", "type": "link", "text": "this tool", "internal": false, "nofollow": false, "status": null, "error": "ENOTFOUND", "finalUrl": "https://some-closed-tool.io/" }
  ],
  "blocked": [
    { "url": "https://www.linkedin.com/company/example", "type": "link", "text": "LinkedIn", "internal": false, "nofollow": false, "status": 999 }
  ],
  "redirects": [
    { "url": "http://www.example.com/contact", "type": "link", "text": "Contact", "internal": true, "nofollow": false, "status": 200, "finalUrl": "https://www.example.com/contact/", "hops": 2 }
  ],
  "ok": false,
  "issues": ["2 broken link(s)", "1 link(s) blocked the checker (401/403/429/999), check them by hand"],
  "checkedAt": "2026-10-26T00:00:00.000Z"
}
```

The **Broken links** view of the dataset flattens the result to one line per broken link, ready for a spreadsheet. A `SUMMARY` record in the key-value store counts pages checked, pages with broken links, broken, blocked and redirecting links.

### Pricing

Pay per result: **$0.004 per page delivered** to the dataset ($4 per 1,000 pages), plus the small Apify platform usage of the run. The links checked on a page are included, not charged one by one. With `onlyProblems` on, clean pages are not delivered and not charged.

### Notes

- It reads only the public HTML of the pages you provide and the HTTP status of the links on them. No login, no cookies, no personal data. It does not crawl beyond the pages you list.
- Links injected by JavaScript are not seen: the check reads the raw HTML, like most crawlers do.
- A site that returns 200 for every URL (a parked domain, a soft 404) cannot be detected from the status code alone.

# Actor input Schema

## `urls` (type: `array`):

One page URL per line. Every link on each page is opened. The checker does not crawl further: you choose the pages.

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

Also open links to other domains. Turn off to check only internal links.

## `includeImages` (type: `boolean`):

Also check the src of every <img>.

## `includeAssets` (type: `boolean`):

Also check <script src> and <link rel=stylesheet>.

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

List links that work but go through a redirect (useful after a migration).

## `maxLinksPerPage` (type: `integer`):

Only the first N unique links of each page are checked.

## `onlyProblems` (type: `boolean`):

Skip pages where every link works. You only pay for rows that are delivered.

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

How many pages are checked at the same time.

## `linkConcurrency` (type: `integer`):

How many links of one page are checked at the same time.

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

Give up on a link after this many seconds.

## `maxRetries` (type: `integer`):

How many times a request is retried after a timeout, a 429 or a 5xx response.

## `maxUrls` (type: `integer`):

Safety cap on the number of input lines processed.

## Actor input object example

```json
{
  "urls": [
    "https://crawler-test.com/links/broken_links_internal"
  ],
  "checkExternal": true,
  "includeImages": true,
  "includeAssets": false,
  "reportRedirects": true,
  "maxLinksPerPage": 500,
  "onlyProblems": false,
  "concurrency": 3,
  "linkConcurrency": 10,
  "timeoutSecs": 15,
  "maxRetries": 1,
  "maxUrls": 5000
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `results` (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 = {
    "urls": [
        "https://crawler-test.com/links/broken_links_internal"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("variegated_binturong/broken-link-checker-pages").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 = { "urls": ["https://crawler-test.com/links/broken_links_internal"] }

# Run the Actor and wait for it to finish
run = client.actor("variegated_binturong/broken-link-checker-pages").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 '{
  "urls": [
    "https://crawler-test.com/links/broken_links_internal"
  ]
}' |
apify call variegated_binturong/broken-link-checker-pages --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,variegated_binturong/broken-link-checker-pages"
        }
    }
}
```

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/3YomKLqkJXQ5hpYO5/builds/pZ6Awpfm7atp4SvwG/openapi.json
