# Broken Link Checker (Your Listed Pages) (`plainwren/site-link-checker`) Actor

For your own or authorised sites. Give it up to 500 pages and it checks every link on them once, follows redirects, and lists the broken ones with the page they're on. $0.001 per unique link, and timeouts are free.

- **URL**: https://apify.com/plainwren/site-link-checker.md
- **Developed by:** [Plainwren Studio](https://apify.com/plainwren) (community)
- **Categories:** Marketing, Developer tools, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 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

### What is Broken Link Checker?

**Broken Link Checker** finds the broken links on **pages you list**. It is for your own or authorised sites.

Paste in up to **500 pages**. The Actor reads the links on those pages and checks each unique link once with a light **HEAD** request. It **follows redirects** (up to 5 hops) and tells you which page each broken link is on. It never crawls past your list, never follows on from a link target, never reads sitemaps, obeys robots.txt, never logs in, never sends cookies, and never gets past captchas or bot checks.

Try it: leave the page list empty and click **Start** for a free demo on a built-in sample page. For a real run you must tick **These are my own or authorised sites**.

Bot info (user agent and behaviour): [tools.plainwrenstudio.com/bots/link-checker/](https://tools.plainwrenstudio.com/bots/link-checker/). Support: <support@plainwrenstudio.com>.

### What you get

One dataset row per unique problem link (or every checked link if you turn on **Also output working links**):

- `sourcePage`, `sourcePages` (up to 50 pages that use the same link), `linkUrl`
- `linkType`: page / image / script / css / iframe
- `httpStatus`, `issue` (worst one), `issues`
- `redirectChain` (`{url, status}` per hop), `finalUrl`, `responseMs`
- `foundOnCount`, `checkedAt`

No anchor text, page text, emails or phone numbers.

You also get `SUMMARY`, `REPORT.csv` and `REPORT.html` in the key-value store (grouped by source page, worst first).

**Issues it can report:** `BROKEN_4XX`, `BROKEN_5XX`, `DNS_FAILED`, `CONNECTION_REFUSED`, `DNS_TEMPORARY`, `TIMEOUT`, `TLS_ERROR`, `FETCH_FAILED`, `REDIRECT_CHAIN` (2+ hops), `REDIRECT_LOOP`, `TOO_MANY_REDIRECTS` (over 5), `REDIRECT_TO_HTTP`, `MIXED_CONTENT`, `AUTH_REQUIRED`, `ROBOTS_BLOCKED`, `GUARD_BLOCKED`, `HEAD_NOT_ALLOWED`, `RATE_LIMITED`, `BAD_URL`, `PAGE_NOT_HTML`, `PAGE_TOO_BIG`, `OK`.

### Who is it for?

- 🧑‍💻 Site owners and SEO teams checking their own pages for 404s and other broken links.
- 🔗 Agencies that need a CSV or HTML report for a client's listed URLs.
- 🤖 Developers calling it through the Apify API, integrations or MCP.

### How to check links

1. Open the **Input** tab.
2. Add pages under **Pages to check** (all on one site), or leave empty for the free demo.
3. Tick **These are my own or authorised sites**.
4. Optional: turn outside links or resources on/off, set rate limits, or add include/exclude patterns.
5. Click **Start**.
6. Read the dataset, or download the CSV / HTML report from the key-value store.

#### Input example

```json
{
  "startUrls": [
    "https://tools.plainwrenstudio.com/",
    "https://tools.plainwrenstudio.com/bots/",
    "https://tools.plainwrenstudio.com/about/"
  ],
  "confirmOwnOrAuthorised": true,
  "maxPages": 500,
  "maxLinksChecked": 5000,
  "checkExternalLinks": true,
  "checkResources": true,
  "requestsPerSecond": 1,
  "maxConcurrency": 4,
  "includeUrlPatterns": [],
  "excludeUrlPatterns": [],
  "ignoreQueryStrings": false,
  "reportOkLinks": false,
  "timeoutSecs": 15
}
```

| Field | Type | What it does |
|---|---|---|
| `startUrls` | array of strings | Pages whose links you want checked, max 500, one site. Leave empty for the free demo. |
| `confirmOwnOrAuthorised` | boolean | Required for a real run. If false, the run stops at once and nothing is charged. |
| `maxPages` | integer (default 500) | Only the first N listed pages are used. |
| `maxLinksChecked` | integer (default 5000, max 50000) | Hard cap on unique URLs checked in one run. |
| `checkExternalLinks` | boolean (default `true`) | Check links to other sites (1/s per outside host, never crawled). |
| `checkResources` | boolean (default `true`) | Also check img / script / stylesheet / iframe URLs. |
| `requestsPerSecond` | number (default `1`, 0.2–4) | Polite rate for your own site. |
| `maxConcurrency` | integer (default `4`, 1–10) | How many different hosts at once. |
| `includeUrlPatterns` / `excludeUrlPatterns` | arrays | Optional globs on the full link URL. |
| `ignoreQueryStrings` | boolean (default `false`) | Treat `?utm=…` variants as one URL. |
| `reportOkLinks` | boolean (default `false`) | Also output working links. Charged once either way. |
| `timeoutSecs` | integer (default 15, 1–60) | Timeouts are reported and not charged. |

### How much does it cost?

Pay-per-event pricing:

- **$0.001 per unique link checked** (`link-checked`): **$1.00 per 1,000**. Charged for each unique URL that returns a real HTTP answer (listed pages and link targets), and for link targets whose host does not exist (`DNS_FAILED`) or refuses the connection (`CONNECTION_REFUSED`). A URL found on 50 pages is charged once.
- **Free:** timeouts, temporary DNS errors, TLS and other connection errors, a 429/503 that will not clear, too many redirects, robots blocks, guard blocks, HEAD not allowed, bad URLs, unreadable listed pages (including a listed page that is DNS_FAILED or refused), duplicates, a run refused for a missing confirmation, and the demo run.
- The run stops cleanly before it would go over your maximum charge.

Example: 2,000 unique charged links cost **$2.00** (arithmetic, not a measured run).

Apify's free plan includes monthly platform credit you can use to try it.

### How polite it is

- One request at a time per host. Your site: `requestsPerSecond` (default 1). Outside hosts: always 1 per second.
- Up to 4 hosts at once by default (max 10).
- robots.txt read once per host and obeyed. A 4xx answer (404 included) means no rules. A 429 or 5xx on robots.txt blocks that host for the run (`ROBOTS_BLOCKED`, free). `Crawl-delay` honoured up to 10 s.
- On 429/503: waits `Retry-After` (max 60 s, 2 retries), then `RATE_LIMITED` (free).
- Private, internal and reserved addresses are refused on the first request, on every redirect hop, and again at DNS lookup. Max 5 redirect hops.
- Honest User-Agent: `PlainwrenLinkChecker/1.0 (+https://tools.plainwrenstudio.com/bots/link-checker/)`.

### Limits

- Max **500** listed pages and **50,000** unique URLs checked per run (default cap 5,000).
- Listed pages must be on **one site**.
- Link targets get one HEAD each and are never crawled further.
- No login, cookies, captchas or proxies.

### FAQ

#### Does it crawl my whole site?

No. Only the pages you list are fetched and read. Each link on them gets one HEAD and is not followed on.

#### Why do I have to confirm ownership?

So the Actor is only used on sites you own or are allowed to check. Unticked runs stop immediately and are free.

#### Am I charged for timeouts?

No. Timeouts are free. Dead domains and refused connections are charged, because they are real findings.

#### Can I use it from Make, Zapier, n8n or an AI agent?

Yes, through Apify's integrations, API and MCP.

#### Something looks wrong. What should I do?

Open an issue on the Actor's **Issues** tab (remove anything private) or email <support@plainwrenstudio.com>.

#### WordPress users

For an in-dashboard scan of a WordPress site's own content, see [Local Link Checker](https://tools.plainwrenstudio.com/bots/local-link-checker/) (WordPress plugin; separate product).

# Actor input Schema

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

For your own or authorised sites. The pages whose links you want checked, all on one site (max 500). Only these pages are fetched and read; the links on them get one HEAD request each and are never crawled. Leave empty for a free demo.

## `confirmOwnOrAuthorised` (type: `boolean`):

Required. Tick to confirm you own the site or are allowed to check it. If unticked, the run stops at once and nothing is charged.

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

Only the first N listed pages are used (1-500). Extra pages are skipped and not charged.

## `maxLinksChecked` (type: `integer`):

Hard cap on unique URLs checked in one run, listed pages included (1-50,000). The run stops cleanly and says so in SUMMARY.

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

Check links to other sites with one HEAD each (1 request per second per outside host, never crawled).

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

Also check src/href of img, script, stylesheet links and iframes (HEAD only). Needed for mixed-content findings.

## `requestsPerSecond` (type: `number`):

Polite rate for your own site, one request at a time (0.2-4, default 1). Outside hosts are always 1 per second. robots.txt Crawl-delay (up to 10 s) and 429/503 answers slow it further.

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

How many different hosts are checked at the same time (1-10, default 4). Each host still gets one request at a time.

## `includeUrlPatterns` (type: `array`):

Optional globs on the full link URL, e.g. https://example.com/blog/\*\*. Empty = all.

## `excludeUrlPatterns` (type: `array`):

Optional globs on the full link URL, e.g. **/cart**. Skipped links are not requested or charged.

## `ignoreQueryStrings` (type: `boolean`):

Treat ?utm=... style variants as one URL.

## `reportOkLinks` (type: `boolean`):

By default only problem links are output. Every checked URL is charged once either way.

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

1-60, default 15. A timeout is reported and not charged.

## Actor input object example

```json
{
  "startUrls": [
    "https://tools.plainwrenstudio.com/",
    "https://tools.plainwrenstudio.com/bots/",
    "https://tools.plainwrenstudio.com/about/"
  ],
  "confirmOwnOrAuthorised": true,
  "maxPages": 500,
  "maxLinksChecked": 5000,
  "checkExternalLinks": true,
  "checkResources": true,
  "requestsPerSecond": 1,
  "maxConcurrency": 4,
  "includeUrlPatterns": [],
  "excludeUrlPatterns": [],
  "ignoreQueryStrings": false,
  "reportOkLinks": false,
  "timeoutSecs": 15
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset of checked links with issues (and OK rows when reportOkLinks is on).

## `OUTPUT` (type: `string`):

Compact run totals for the Console.

## `SUMMARY` (type: `string`):

Full check summary JSON.

## `REPORT_html` (type: `string`):

Human-readable HTML report.

## `REPORT_csv` (type: `string`):

CSV export of link rows.

# 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://tools.plainwrenstudio.com/",
        "https://tools.plainwrenstudio.com/bots/",
        "https://tools.plainwrenstudio.com/about/"
    ],
    "confirmOwnOrAuthorised": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("plainwren/site-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://tools.plainwrenstudio.com/",
        "https://tools.plainwrenstudio.com/bots/",
        "https://tools.plainwrenstudio.com/about/",
    ],
    "confirmOwnOrAuthorised": True,
}

# Run the Actor and wait for it to finish
run = client.actor("plainwren/site-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://tools.plainwrenstudio.com/",
    "https://tools.plainwrenstudio.com/bots/",
    "https://tools.plainwrenstudio.com/about/"
  ],
  "confirmOwnOrAuthorised": true
}' |
apify call plainwren/site-link-checker --silent --output-dataset

```

## MCP server setup

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