# Broken Anchor Checker & Repair Suggestions (`alex_fodo/broken-anchor-checker`) Actor

Find missing HTML section anchors even when pages return HTTP 200. Get source links, evidence and existing replacement candidates. No AI API key.

- **URL**: https://apify.com/alex_fodo/broken-anchor-checker.md
- **Developed by:** [AGENT DOJO](https://apify.com/alex_fodo) (community)
- **Categories:** SEO tools, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 html section 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 Anchor Checker & Repair Suggestions

Find links to missing HTML sections even when the target page returns **HTTP 200**. Get the source page, link text, verification evidence and up to three existing replacement anchors to review.

Useful after documentation updates, heading changes, content publication and website migrations. No AI key, browser, proxy or separate subscription is required by this Actor. Apify charges depend on the configured Store pricing.

### Start here

1. Choose **Extract links from pages** and paste the pages you are permitted to check, or choose **Check exact deep links** and paste URLs containing `#section`.
2. Run the Actor. Enable **Check links to other origins** if Pages mode should also check external references.
3. Open the **HTML repair report**. Review `missing_in_html`, then inspect any replacement candidates before editing your site.

The default **Fictional demo** uses built-in HTML examples. It makes no network requests, is labelled `isDemo: true`, and is not evidence of compatibility with a live website. Switch modes and enter your URLs for real checks.

#### Exact links example

```json
{
  "mode": "links",
  "deepLinks": ["https://www.sitemaps.org/protocol.html#xmlTagDefinitions"],
  "maxLinks": 10,
  "maxRequests": 10
}
```

#### Pages example

```json
{
  "mode": "pages",
  "pageUrls": ["https://www.sitemaps.org/protocol.html"],
  "includeExternal": false,
  "maxPages": 1,
  "maxLinks": 30
}
```

These are public smoke-check inputs, not promises that a third-party site will remain available. There is no recursive site crawl or sitemap discovery: Pages mode reads only the source URLs supplied.

### What you get

Each link result contains `status`, `sourceUrl`, `linkText`, `targetUrl`, `finalUrl`, `httpStatus`, `fragment`, `matchedId`, `reason`, `suggestions`, bounded `sources`, `occurrences`, `checkedAt`, `verificationMode` and `isDemo`.

| Status | Meaning |
|---|---|
| `found` | Matching HTML ID / legacy named anchor, or a supported document-top link |
| `missing_in_html` | No matching target in the complete fetched HTML; JavaScript may add it later |
| `target_unavailable` | Target returned HTTP 404 or 410; no fragment check charged |
| `unverified` | Timeout, access restriction, server error, interrupted response or another verification failure |
| `skipped` | Robots policy, disabled external checks or a target-document limit |
| `unsupported` | Non-HTML, unsupported charset, hash-router or text-fragment URL |
| `no_fragment` | A directly supplied URL has no section identifier |
| `invalid_input` | Malformed, credential-bearing, local or otherwise unacceptable URL |

Source-page failures use `recordType: "source_error"`. A source with no fragment links is a legitimate zero-result check, with an uncharged summary row.

The default key-value store contains **REPORT** (HTML), **SUMMARY** (coverage, limits and billing), and **RESULTS** (full JSON). Dataset rows can be exported through Apify. `complete` refers only to the supplied scope; inspect skipped/unverified results and limits before treating a report as complete.

### Replacement candidates

Suggestions are ranked by lexical overlap between the old fragment, link text and existing IDs/headings. Every candidate points to an ID or named anchor found in the fetched document. A similarity score is **not** a probability or a guarantee of the same meaning. No website is edited automatically.

Candidate ranking is bounded to the first 2,000 IDs/named anchors; `suggestionSearchLimited` reports a larger document. Exact fragment matching still uses all parsed IDs. Source extraction is capped at 10,000 fragment links per supplied page and reports limited coverage if exceeded.

### Pricing contract

Primary event: **`fragment-checked`**. One event per unique completed live check with status `found` or `missing_in_html`. Multiple references to the same normalized target URL and fragment share one check. Fragments on the same document reuse its HTML.

Fictional demo results, diagnostic rows, unavailable targets, fetch errors, unsupported and skipped links do not trigger this event. The Store Pricing tab is authoritative for the actual price. This implementation requires automatic per-dataset-item and Actor-start fees to be disabled so those promises remain true.

The Actor stops when the event budget or work limits are reached. Platform usage in a PPE + usage configuration, if selected by the publisher, is separate from the event budget. Review the Pricing tab before running.

### Limits and privacy

- Static UTF-8/ASCII HTML only. No JavaScript execution, login, cookies, CAPTCHA bypass or rendered visibility check.
- Ordinary HTML anchors and legacy `a[name]` are supported. PDF/SVG fragments, text fragments and hash-router routes are outside this version's scope.
- Requests respect robots.txt and declared crawl delays. If robots policy cannot be verified, the origin is not crawled. Standard ports only; private/reserved network addresses and unsafe redirect destinations are blocked.
- Default limits: 50 source pages, 300 unique links, 200 target documents, 500 HTTP requests, 2 MB/document and 30 MB/run. Requests are sequential with at least 500 ms between requests to an origin.
- Redirects and robots requests consume the request allowance. No proxy is used. Compressed responses that ignore the identity request are unverified.
- Source references are capped at 25 per unique target; `occurrences` counts additional references. Limits and interrupted work are reported explicitly.
- Input and output live in your Apify run storage. URLs, including query parameters, can be sensitive: use public URLs without secrets. Records are not sent to third-party AI services and page bodies are not logged.
- `missing_in_html` does not prove a broken JavaScript-generated target. This is a section-link QA tool, not a guarantee of accessibility, indexing, rankings or sales.

### API / n8n

Start this Actor using its Apify API endpoint and the same JSON input. After the run, read dataset items or the SUMMARY/REPORT records. In n8n, use the Apify integration to run it after content publication, then branch on `status === "missing_in_html"`. Check summary coverage before sending a success notification.

### Local development

Requires Node.js 22 or newer.

```sh
npm ci --ignore-scripts
npm test
npm run check
npm run demo
## Optional public network test; no paid API calls:
npm run smoke:live
## Run your input locally:
node scripts/run-local.js examples/input.links.json
```

Local reports go into `output/`. `npm start` runs the Apify SDK entry point; without a cloud environment it uses local storage. Cloud builds and owner-run cloud tests consume your own Apify credits.

# Actor input Schema

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

Demo: account-free fictional examples. Pages: extract fragment links from the pages you supply. Links: check exact deep links.

## `pageUrls` (type: `array`):

Pages mode: public HTTP(S) URLs you own or are permitted to check. Only these pages are read for source links. No recursive crawl.

## `deepLinks` (type: `array`):

Links mode: exact URLs such as https://your-site.example/docs#installation. Supplied links are explicit targets; includeExternal applies only in Pages mode.

## `includeExternal` (type: `boolean`):

Pages mode only. Otherwise external targets are reported as skipped. Each target origin's robots.txt is respected.

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

Pages mode: limit the number of supplied source pages read.

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

Limits selected target+fragment pairs. Repeated references do not cause duplicate paid checks.

## `maxTargetPages` (type: `integer`):

Maximum distinct document URLs checked; multiple fragments reuse downloaded HTML.

## `maxSuggestions` (type: `integer`):

Maximum lexical suggestions per missing fragment. All IDs exist in the fetched HTML; semantic equivalence is not guaranteed.

## `requestDelayMillis` (type: `integer`):

Sequential, polite HTTP requests. A longer robots crawl-delay is honoured.

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

Includes DNS, connection and response. Failed requests are not fragment checks.

## `maxRunSecs` (type: `integer`):

Stops selecting more work and reports incomplete coverage. Leave platform timeout above this deadline.

## `maxRequests` (type: `integer`):

Includes robots.txt and redirect hops.

## `maxRedirects` (type: `integer`):

Every hop is checked against public-address and robots rules.

## `maxResponseBytes` (type: `integer`):

Oversized or interrupted responses are unverified, never missing anchors.

## `maxTotalBytes` (type: `integer`):

Bounds total downloaded bytes within a run.

## Actor input object example

```json
{
  "mode": "demo",
  "pageUrls": [],
  "deepLinks": [],
  "includeExternal": false,
  "maxPages": 50,
  "maxLinks": 300,
  "maxTargetPages": 200,
  "maxSuggestions": 3,
  "requestDelayMillis": 500,
  "requestTimeoutSecs": 15,
  "maxRunSecs": 180,
  "maxRequests": 500,
  "maxRedirects": 5,
  "maxResponseBytes": 2000000,
  "maxTotalBytes": 30000000
}
```

# Actor output Schema

## `report` (type: `string`):

No description

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

No description

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

No description

## `dataset` (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 = {
    "mode": "demo"
};

// Run the Actor and wait for it to finish
const run = await client.actor("alex_fodo/broken-anchor-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 = { "mode": "demo" }

# Run the Actor and wait for it to finish
run = client.actor("alex_fodo/broken-anchor-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 '{
  "mode": "demo"
}' |
apify call alex_fodo/broken-anchor-checker --silent --output-dataset

```

## MCP server setup

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