# Broken Link Quick Check — 5 Pages, 50 Links, Same-Origin (`pontio/broken-link-quick-check`) Actor

Audit supplied sites within 5 pages and 50 same-origin targets per seed. Observed 404/410 with source/anchor evidence; incomplete audits uncharged.

- **URL**: https://apify.com/pontio/broken-link-quick-check.md
- **Developed by:** [Gabor Molnar](https://apify.com/pontio) (community)
- **Categories:** Developer tools, SEO tools, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $7.00 / 1,000 bounded site audits

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 Quick Check — 5 Pages, 50 Links, Same-Origin

Supply up to **3 seed URLs** you own or are authorized to audit. Each produces one sequential, bounded audit row with observed anchor/source-page evidence and target checks. Only HTTP **404/410 actually observed** is labelled broken. A clean bounded sample does **not** prove the entire website has no broken links.

```json
{"urls":["https://www.w3.org/Style/Examples/007/center-example.en.html"]}
```

### Coverage and output

Per seed: at most **5 candidate page fetches** (up to 5 same-origin redirects per fetch), discovery **depth 2**, **50 distinct canonical link targets**, **100 anchor evidence occurrences**, **4 MiB per body** (a larger complete-2xx child page still counts as answered by its status, but its links are not read: `bytes` truncation; an oversized seed or partial 206 response is incomplete and free), and a **60-second total deadline**. HTML must identify `text/html` or `application/xhtml+xml`. Only static `a[href]` links are discovered; relative URLs respect the first HTML base URL. Fragments are ignored. Paths and queries remain distinct. Canonical repeated seeds are audited once, within the raw 3-input cap.

`pages` lists HTML read. Join `evidence[].target` to `checks[].target` for source-page/anchor evidence of broken targets. `counts` reports observed targets, broken, unknown, excluded, and skipped external/invalid links. `truncation` names intentional page/depth/target/evidence caps, the site's own crawl delay (`crawl_delay`), or a deadline. Evidence includes non-broken links so checks are auditable. A target GET already observed during discovery supplies its status without a second HEAD check.

No external-origin checks (including redirect hops), browsers, JavaScript rendering, sitemaps, discovery beyond supplied seeds, state, monitoring, assets or form submission. If the seed itself lives on another origin (`python.org` → `www.python.org`, HTTP → HTTPS), the audit moves there once, re-reading that origin's robots.txt. After that, redirects stay on that exact origin: a link that redirects to another origin is listed as `excluded` (`redirects_off_origin`, with the observed status and destination) and is not followed. Every outgoing hop validates **all resolved IP addresses**, pins the connection to a validated public IP and keeps TLS hostname verification enabled. The honest user agent is `Broken-Link-Quick-Check/0.1`.

Robots exclusions are checked before page/target requests and redirect hops; advertised crawl delay is respected. A 429 answer is read the same way, as the site asking for a slower pace: from then on requests are 3 seconds apart, and the refused URL is asked once more. When the delay leaves no time for the remaining targets within the 60 seconds, they are listed as `excluded` (`crawl_delay`) without requests and the audit is still a completed, disclosed sample. A robots.txt that answers 4xx (other than 429), or one with no rules at all (only a Sitemap line, say), means no rules, as [RFC 9309](https://www.rfc-editor.org/rfc/rfc9309.html#section-2.3.1.3) specifies; Allow/Disallow lines outside any user-agent group are not guessed at. **A 429, 5xx, unreachable or malformed robots.txt never means permission to crawl.** Excluded targets are listed without requests. A denied seed is free. Unknown checks (403/429/5xx, TLS/transport/body failures, unsafe or robots-disallowed redirects) are **not** broken. Incomplete audits preserve available evidence and are uncharged. An audit that checked no link at all is uncharged too: a page with no static `a[href]` same-origin links (usually a JavaScript-rendered site, or one whose links are all `http://` while the seed is `https://`) answers nothing about broken links.

### Billing

**$0.01 per completed bounded audit**, one `site-links-audited` event per answered seed, not per page, link or broken result. That is the Free-plan price. Paid Apify plans get a Store discount: $9.00 per 1,000 on Starter, $8.00 on Scale and $7.00 on Business and above. Intentional disclosed coverage caps and robots exclusions may still leave a completed bounded audit billable; upstream-incomplete audits are free, even with useful retained evidence.

| reasonCode | Meaning | Charged? |
|---|---|---|
| `audited` | Bounded audit completed, possibly no broken links or intentional coverage truncation | yes |
| `invalid_url` | Invalid/unsupported seed | no |
| `unsafe_target` | Unsafe DNS/IP seed or robots destination | no |
| `robots_unreadable` | robots.txt was 429/5xx/unreachable/malformed; no crawl | no |
| `robots_denied` | Seed excluded by robots | no |
| `not_html` | Seed did not identify HTML | no |
| `no_links_checked` | Audit finished but checked no same-origin link (none found, or every one excluded) | no |
| `upstream_incomplete` | Crawl or target checks incomplete; available evidence retained | no |

### Access and sample attribution

Caller is responsible for website terms, authorized access and content reuse rights. Public HTTP availability and robots allowance are **not** blanket permission over third-party content. This is diagnostic link/status evidence, not a page-content redistribution service. [RFC 9309](https://www.rfc-editor.org/rfc/rfc9309.html) explicitly distinguishes robots from authorization.

The prefill uses a public [CSS example](https://www.w3.org/Style/Examples/007/center-example.en.html) and its linked public examples. Sample attribution: Copyright © 1994–2025 World Wide Web Consortium (W3C®); [copyright policy](https://www.w3.org/copyright/) and [document licence](https://www.w3.org/copyright/document-license-2023/) apply. The licence permits copies/portions for any purpose with source/copyright/status notices; no endorsement is implied. Sample source URLs accompany each evidence row. This permission is limited to that sample, not arbitrary customer sites.

# Changelog

This Actor's version history is a separate document: https://apify.com/pontio/broken-link-quick-check/changelog.md

# Actor input Schema

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

Up to 3 supplied seeds. Canonical repeats including fragments are audited once; paths and queries stay distinct. Sequential bounded same-origin audits only, not whole-site completeness. Caller is responsible for access/content rights.

## Actor input object example

```json
{
  "urls": [
    "https://www.w3.org/Style/Examples/007/center-example.en.html"
  ]
}
```

# Actor output Schema

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

Results produced by this run, in its default dataset.

# 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://www.w3.org/Style/Examples/007/center-example.en.html"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("pontio/broken-link-quick-check").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://www.w3.org/Style/Examples/007/center-example.en.html"] }

# Run the Actor and wait for it to finish
run = client.actor("pontio/broken-link-quick-check").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://www.w3.org/Style/Examples/007/center-example.en.html"
  ]
}' |
apify call pontio/broken-link-quick-check --silent --output-dataset

```

## MCP server setup

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

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/mdFlkF2oi37aekyMk/builds/xSUf895IV66Oa0oh6/openapi.json
