# Broken Link & Redirect Checker – 404s, Redirect Chains, Anchors (`tesselyn/broken-link-checker`) Actor

Crawl a website (or check a URL list) and find broken links (404/410/5xx/DNS/SSL), redirect chains and loops, http→https hops and missing #anchors, with the source page and anchor text of every issue. Polite, robots.txt-compliant. ~$1 per 1,000 links.

- **URL**: https://apify.com/tesselyn/broken-link-checker.md
- **Developed by:** [Tesselyn](https://apify.com/tesselyn) (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 $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

## Broken Link & Redirect Checker – 404s, Redirect Chains, Anchors

Find every **broken link, redirect chain and missing #anchor** on a website, with the **page it's on and its anchor text**, so you know exactly what to fix. Give it a website and it crawls it, or give it a list of URLs and it checks each one. It is polite and follows robots.txt. You pay **only for links that got a definite answer**.

### What does the Broken Link & Redirect Checker do?

- **Crawls your site** (same domain, up to the page limit and depth you set), pulls out every link and checks each unique URL once, both **internal and external**.
- **Finds broken links**: 404 / 410 / other 4xx, 5xx, DNS failures (dead domains), SSL/TLS errors, refused connections, redirect loops and too-many-redirects.
- **Analyses redirects**: number of hops, the full chain (URL + status for each hop), loops, temporary (302/307) vs permanent (301/308), **http → https** hops and the final URL. Internal links that redirect are flagged so you can point them straight at the final URL.
- **Optional anchor check**: reports `/page#section` links where the target page has no `id`/`name="section"`. It skips SPA routes (`#/…`, `#!…`), `#top` and text fragments, so you don't get false alarms.
- **Optional resource check**: images, scripts, stylesheets, icons, canonical/alternate links, iframes, video/audio sources and `srcset`.
- **Tells you where each issue is**: source page, anchor text, link type, how many pages link to the URL, and up to 10 source pages.
- Writes **one summary row per site**: pages crawled, links checked, broken internal/external links, redirect chains and loops, missing anchors, the top 20 broken links by number of referring pages, and which limit stopped the crawl.

#### Fewer false alarms than typical link checkers

- **403 / 401 / 429 / 999 (bot protection or rate limiting) and timeouts are reported as `unverified`, not `broken`, and are not charged.** Many sites (LinkedIn, some CDNs) block automated checks. Calling those links "broken" is the most common complaint about link checkers.
- **Retries** after network errors, 429 (honouring `Retry-After` up to 10 s) and 502/503/504 before reaching a verdict.
- **HEAD first, then GET** if the server rejects HEAD or answers it with an error, because some servers handle HEAD badly.
- **The run deadline is respected**: when your run timeout gets close, the Actor stops and saves everything it has checked so far instead of losing the run.

#### Check one page free

For a single page (up to 40 unique links), use the free [broken link checker on tesselyn.com](https://tesselyn.com/tools/broken-link-checker/?utm_source=apify\&utm_medium=readme\&utm_campaign=broken-links). It doesn't follow redirects or crawl. Use this Actor to crawl a whole site, follow redirect chains and check #anchors.

### Who is it for?

SEO specialists and agencies (site audits, migrations), webmasters and content teams (documentation and blogs), developers (pre-release checks, CI via the API) and anyone who needs to verify a list of URLs: backlink targets, redirect maps after a migration, or sitemap URLs.

### How to use it

1. Add one or more **Start URLs**.
2. Choose a **mode**: *Crawl* (follow links within the site) or *List* (check exactly the URLs you gave).
3. Set **Max links to check**. This is the cost cap. Set **Max pages per site** too.
4. Run it, then open the **Issues** view in the dataset, or download CSV/Excel/JSON.

#### Input example

```json
{
  "startUrls": [{ "url": "https://www.example.com/" }],
  "mode": "crawl",
  "maxPagesPerSite": 100,
  "maxDepth": 3,
  "maxLinksToCheck": 2000,
  "checkExternalLinks": true,
  "checkFragments": true,
  "onlyReportIssues": true
}
```

### Output example

One row per unique URL:

```json
{
  "type": "link",
  "url": "https://www.example.com/old-pricing",
  "verdict": "broken",
  "category": "not_found",
  "isBroken": true,
  "status": 404,
  "finalUrl": "https://www.example.com/old-pricing",
  "redirectCount": 0,
  "redirectChain": [],
  "issues": ["BROKEN_404"],
  "isInternal": true,
  "linkType": "a",
  "sourcePage": "https://www.example.com/blog/2023-update",
  "anchorText": "see our pricing",
  "sourceCount": 3,
  "sources": [{ "page": "https://www.example.com/blog/2023-update", "anchorText": "see our pricing", "linkType": "a" }],
  "charged": true
}
```

A redirect row includes `"redirectChain": [{"url": "http://example.com/a", "status": 301, "location": "https://example.com/a"}, …]`, `"httpToHttps": true` and issue codes such as `REDIRECT_CHAIN`, `TEMPORARY_REDIRECT` and `INSECURE_HTTP`.

**Verdicts:** `ok` · `redirected` · `broken` · `unverified` (blocked, rate-limited or timed out, so it could not be confirmed) · `skipped` (disallowed by robots.txt, or not a public address) · `not_checked` (the link limit was reached).

### How much does it cost?

Pay per event: **$0.001 per link checked**, which is **$1 per 1,000 links**. Only URLs with a definite verdict (`ok`, `redirected` or `broken`) are charged. **Unverified, skipped and not-checked URLs, input errors and summary rows are free.** Each unique URL is charged once per run, however many pages link to it. Pages crawled to discover links cost nothing extra. Set *Max links to check* (and, optionally, the run's maximum charge) to cap the cost.

### Integrations and API

Run it from the Apify API, the CLI, Make, Zapier or n8n, or put it on a **Schedule** for weekly monitoring and get an email or Slack alert via webhooks when `broken` > 0. The `OUTPUT` record and the per-site summary row make dashboards easy.

### Responsible use

The Actor identifies itself as `TesselynLinkCheckBot/1.0 (+https://apify.com/tesselyn)` and does **not** impersonate browsers or use proxies to get around bot protection. It **obeys robots.txt (RFC 9309, including wildcards and Crawl-delay up to 10 s) for every host it contacts**, external hosts included. It sends at most one request at a time to each host, waits at least 500 ms between requests to the same host (configurable up to 10 s) and limits total parallel requests (default 10, max 30). It only reads HTML pages to extract links (5 MB cap per page). Other URLs are checked with HEAD, or with a GET whose body is discarded. JavaScript is not executed. Private, loopback and cloud-metadata addresses are refused.

### Terms of use

These terms add to the [Apify Standard Actor Contract](https://docs.apify.com/legal/standard-actor-contract), which governs your use of this Actor; they do not replace it.

- Only crawl sites you own, manage or otherwise have a legitimate interest and right to check. You are responsible for making sure your use of the Actor and its output complies with applicable laws and the target websites' terms.
- Do not use the Actor to probe internal networks, to load-test or stress sites, or to get around access controls. Abusive use may be blocked.
- **Sanctions and restricted countries.** You may not use this Actor if you are located in, ordinarily resident in, or acting on behalf of a person or organisation in a restricted country or region. Currently these are: Afghanistan, Belarus, Central African Republic, Cuba, Democratic Republic of the Congo, Haiti, Iran, Iraq, Lebanon, Libya, Mali, Myanmar, Nicaragua, North Korea, Russia, Somalia, South Sudan, Sudan, Syria, Venezuela, Yemen, Zimbabwe, Antarctica, and the Crimea, Sevastopol, Donetsk, Luhansk, Kherson and Zaporizhzhia regions of Ukraine. You also may not use it if you are on, or owned or controlled by someone on, any applicable sanctions list (including those of the United Nations, the United States (OFAC SDN), the European Union, the United Kingdom or Israel). We may block such use, and we may update this list when the law or our policy changes.
- Output is an automated, best-effort technical check and is provided "as is". A link reported as working or broken may behave differently for other clients, locations or times. Check results before relying on them for important decisions.
- **No warranty; limitation of liability.** To the maximum extent permitted by applicable law, the Actor and its output are provided "as is" and "as available", without warranties of any kind, express or implied (including accuracy, completeness, merchantability, fitness for a particular purpose and non-infringement), as set out in section 7 of the Apify Standard Actor Contract. The Actor creator (Tesselyn) is not liable for any indirect, incidental, special, consequential or punitive loss or damage, including lost traffic, rankings, revenue, profits or data, arising from use of or reliance on the Actor or its output. Any other liability is limited as set out in section 8 of that Contract, and nothing here limits liability that cannot be limited under applicable law. Output is for information only and is not SEO, legal or other professional advice.

### Privacy notice

- **What is processed:** the URLs you submit, plus public technical data about the links found on the crawled pages: URL, HTTP status, redirect chain, response time, content type and source page. **Anchor text** is stored up to 200 characters and can sometimes include a person's or business name. `mailto:` and `tel:` links are counted but **never stored**. No page content, emails, phone numbers or other personal data are extracted.
- **Where it is stored:** only in your own Apify run storage (default dataset, `OUTPUT` record, run log and input), under your Apify account and Apify's retention settings. Apart from the HTTP requests to the sites and links being checked, the Actor sends nothing to third parties and stores nothing anywhere else.
- **Roles:** for any personal data in your input or output, you are the controller and the Actor creator acts as processor, as set out in section 5 of the Apify Standard Actor Contract. Run logs contain only counts and error codes.
- **Deletion:** delete the run or dataset in Apify Console at any time. For questions or privacy requests, open an issue on the Actor's Issues tab.

### FAQ

#### Why is a link "unverified" and not "broken"?

The server answered 401/403/407/429/451/999 or didn't answer within the timeout. That usually means bot protection or rate limiting, not a dead page. Check these by hand in a browser. They are not charged.

#### Why was a link "skipped"?

That host's robots.txt disallows our crawler (`TesselynLinkCheckBot`), its robots.txt returned 5xx/429 (RFC 9309 says to treat that as "disallow all"), or the URL points to a non-public address. To check your own site, allow `TesselynLinkCheckBot` in your robots.txt.

#### Does it render JavaScript?

No. It reads the HTML your server sends. Links inserted only by JavaScript are not found.

#### How long does a run take?

Requests to the same host are spaced at least 0.5 s apart, so a site with 1,000 internal URLs takes about 10 minutes. External links on many different hosts are checked in parallel. Set a longer run timeout for big sites. The Actor saves partial results before the timeout.

#### Does the same URL get charged twice?

No. Each unique URL (fragment removed) is checked and charged once per run.

#### How much does a 10,000-link site cost?

At most about $10 ($0.001 per link checked), plus the tiny per-run start fee. Only unique URLs with a definite verdict are charged, so unverified, skipped and not-checked links lower the total. Set *Max links to check* to cap it.

### Related Tesselyn tools

- [AI Crawler & llms.txt Checker](https://apify.com/tesselyn/ai-search-readiness-auditor): see which AI crawlers (GPTBot, OAI-SearchBot, ClaudeBot, PerplexityBot and more) a site's robots.txt allows, check llms.txt and get a 0–100 GEO readiness score.
- Domain Health Checker: domain expiry, DNS, SPF/DMARC/DKIM, SSL and HSTS in bulk. Coming to the Apify Store soon. Until then, try the free [domain and email health checker on tesselyn.com](https://tesselyn.com/tools/domain-health-checker/).
- Bulk PageSpeed & Core Web Vitals Checker: Google PageSpeed Insights scores for many URLs. Coming to the Apify Store soon.
- See [all Tesselyn Actors on the Apify Store](https://apify.com/tesselyn) and the [free single-site tools on tesselyn.com](https://tesselyn.com/tools/).

### Support

Built and maintained by **Tesselyn**. Found a bug or a false positive, or need a feature? Open an issue on the Actor's **Issues** tab. Feedback is welcome.

Website: [Tesselyn website (tesselyn.com)](https://tesselyn.com)

# Actor input Schema

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

Crawl mode: the website(s) to crawl (up to 100 start URLs; each is crawled within its own domain). List mode: the exact URLs to check (up to 10,000). Only public http(s) URLs on ports 80/443 are accepted.

## `mode` (type: `string`):

Crawl: fetch pages of the same site starting from each start URL, extract links and check them. List: check each provided URL once (status, redirect chain) without crawling.

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

Hard cap on unique URLs checked in the whole run. This bounds your cost: you pay only for links checked with a definitive result.

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

Crawl mode: maximum internal pages fetched and parsed for links, per site.

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

Crawl mode: 0 = only the start page's links; 1 = also pages linked from it, and so on.

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

Also check links pointing to other domains (they are checked, never crawled).

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

Also check <img>, <script>, \<link rel=stylesheet/icon/canonical>, <iframe>, <source>/<video>/<audio> and srcset URLs.

## `checkFragments` (type: `boolean`):

Report links like /page#section where the target page has no element with id/name 'section'. Same-site HTML pages only; SPA routes (#/...), #top and text fragments are ignored.

## `includeSubdomains` (type: `boolean`):

If on, blog.example.com is crawled as part of example.com. www. is always treated as the same site.

## `onlyReportIssues` (type: `boolean`):

If on, healthy (200 OK, no redirect) links are not written to the dataset (they are still counted and charged as checked). The per-site summary is always written.

## `includeUncheckedLinks` (type: `boolean`):

If on, links found but not checked because 'Max links to check' was reached are listed as free 'not_checked' rows. Off: they are only counted in the site summary (notChecked).

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

Per request. Timeouts are retried once and then reported as 'unverified' (not broken, not charged).

## `minDelayPerHostMs` (type: `integer`):

Politeness: at most one request in flight per host, with this pause between them. A larger robots.txt Crawl-delay (up to 10 s) is always honoured.

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

Total parallel requests across different hosts.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://crawler-test.com/links/broken_links_internal"
    },
    {
      "url": "https://crawler-test.com/links/broken_links_external"
    }
  ],
  "mode": "crawl",
  "maxLinksToCheck": 60,
  "maxPagesPerSite": 3,
  "maxDepth": 3,
  "checkExternalLinks": true,
  "checkResources": false,
  "checkFragments": true,
  "includeSubdomains": false,
  "onlyReportIssues": false,
  "includeUncheckedLinks": false,
  "requestTimeoutSecs": 15,
  "minDelayPerHostMs": 500,
  "maxConcurrency": 10
}
```

# Actor output Schema

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

All rows in the default dataset.

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

Run-level summary JSON.

# 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/links/broken_links_internal"
        },
        {
            "url": "https://crawler-test.com/links/broken_links_external"
        }
    ],
    "maxLinksToCheck": 60,
    "maxPagesPerSite": 3,
    "checkFragments": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("tesselyn/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/links/broken_links_internal" },
        { "url": "https://crawler-test.com/links/broken_links_external" },
    ],
    "maxLinksToCheck": 60,
    "maxPagesPerSite": 3,
    "checkFragments": True,
}

# Run the Actor and wait for it to finish
run = client.actor("tesselyn/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/links/broken_links_internal"
    },
    {
      "url": "https://crawler-test.com/links/broken_links_external"
    }
  ],
  "maxLinksToCheck": 60,
  "maxPagesPerSite": 3,
  "checkFragments": true
}' |
apify call tesselyn/broken-link-checker --silent --output-dataset

```

## MCP server setup

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