# Broken Link Checker \[Only $1💰] | Bulk URL Status | Monitor (`ahmed_jasarevic/bulk-url-checker`) Actor

Bulk-check thousands of URLs for broken links, redirects, response time and SSL expiry. Monitor mode alerts you when a link breaks. Pay per event.

- **URL**: https://apify.com/ahmed\_jasarevic/bulk-url-checker.md
- **Developed by:** [Ahmed Jasarevic](https://apify.com/ahmed_jasarevic) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.90 / 1,000 url checkeds

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 Checker & URL Health Monitor

**Bulk-check up to 10,000 URLs per run for broken links, redirect chains, response time and SSL expiry — then switch to Monitor mode to get alerted the moment a link breaks.** A pay-per-event Apify Actor built for SEO audits, link checking and uptime monitoring without writing code.

### What does Broken Link Checker do?

Paste a list of URLs (or bare domains) and get one structured row per URL with its final HTTP status, health classification (`ok`, `broken_4xx`, `broken_5xx`, `timeout`, `refused`, `dns_error`, `ssl_error`, `too_many_redirects`), the full redirect chain, response time in milliseconds and — optionally — TLS certificate expiry. In **Monitor mode** the Actor remembers the previous state of every URL (in a persistent named key-value store) and reports only what changed, with an optional JSON webhook to Slack, n8n, Zapier or your own endpoint. Runs through the [Apify platform](https://apify.com), so you get API access, scheduling, integrations and run history for free.

### Why use Broken Link Checker?

- **SEO & broken-link audits** — find every 404/410 across a sitemap or exported link list and fix what hurts rankings.
- **Uptime & health monitoring** — schedule it hourly/daily; Monitor mode records state between runs and alerts only on changes.
- **Migration & redesign QA** — verify redirect chains after a site migration before search engines re-crawl.
- **SSL certificate watchdog** — `includeSsl` reports `validTo` and days-left per HTTPS host so certificates never silently expire.
- **Affordable at scale** — pay-per-event pricing ($0.001 per definitive check, $0.002 per change alert); rate-limited checks are free.

### How to use Broken Link Checker

1. Open the Actor's **Input** tab.
2. Paste your URLs (one per line, up to 10,000). Bare domains are upgraded to `https://` automatically; duplicates are removed.
3. Choose **Mode**: `Check` for a one-shot audit, `Monitor` to diff against the previous run (set a **Monitor name** and schedule it).
4. Optionally enable **Follow redirects**, **SSL certificates** and a **Webhook URL**.
5. Press **Start** and wait. Results appear in the **Dataset** (Overview / Broken views) and can be exported as JSON, CSV, Excel or HTML.

### Input

| Field             | Type     | Required | Default   | Notes                                                                                                |
| ----------------- | -------- | -------- | --------- | ---------------------------------------------------------------------------------------------------- |
| `urls`            | string\[] | ✅       | —         | Up to 10,000 URLs. Accepts array or newline/comma-separated string. Scheme auto-added as `https://`. |
| `mode`            | enum     | ❌       | `check`   | `check` (one-shot audit) or `monitor` (diff + alerts).                                               |
| `monitorName`     | string   | ❌       | `default` | Namespaces persisted state so multiple monitors don't collide.                                       |
| `followRedirects` | boolean  | ❌       | `true`    | Follow 3xx to the final destination and record the chain.                                            |
| `maxRedirects`    | integer  | ❌       | `10`      | Max hops (0–20) before `too_many_redirects`.                                                         |
| `timeoutSecs`     | integer  | ❌       | `20`      | Per-request timeout (1–60 s). Slow URLs → `timeout`.                                                 |
| `includeSsl`      | boolean  | ❌       | `false`   | Read TLS cert expiry per unique HTTPS host.                                                          |
| `webhookUrl`      | string   | ❌       | —         | Monitor mode: JSON POST per health change.                                                           |
| `maxConcurrency`  | integer  | ❌       | `10`      | Parallel checks (1–50).                                                                              |
| `delayMs`         | integer  | ❌       | `0`       | Pause after each check (0–5000 ms) for fragile targets.                                              |

### Output

Each run pushes one object per input URL to the default dataset:

```json
{
    "url": "https://example.com/pricing",
    "finalUrl": "https://example.com/pricing",
    "status": 200,
    "ok": true,
    "classification": "ok",
    "redirectCount": 1,
    "redirectChain": [{ "url": "http://example.com/pricing", "status": 301 }],
    "responseMs": 143,
    "sslValidTo": "2026-12-01T12:00:00.000Z",
    "sslDaysLeft": 70,
    "previousClassification": null,
    "changed": null,
    "checkedAt": "2026-09-23T10:15:30.123Z"
}
```

In Monitor mode, changed rows add `previousClassification` and `changed: true`. Invalid inputs produce a row with `classification: "invalid"` (never charged). Download the dataset in JSON, HTML, CSV or Excel.

#### Data table

| Field                                | Type             | Meaning                        |
| ------------------------------------ | ---------------- | ------------------------------ |
| `url`                                | string           | Input URL as checked           |
| `finalUrl`                           | string/null      | URL after redirects            |
| `status`                             | integer/null     | Final HTTP status              |
| `ok`                                 | boolean/null     | Healthy (2xx/3xx)              |
| `classification`                     | enum             | Health verdict (see above)     |
| `redirectCount` / `redirectChain`    | integer / array  | Redirect hops and full chain   |
| `responseMs`                         | integer/null     | Round-trip time                |
| `sslValidTo` / `sslDaysLeft`         | string / integer | TLS expiry (with `includeSsl`) |
| `previousClassification` / `changed` | string / boolean | Monitor-mode diff              |
| `checkedAt`                          | string           | ISO timestamp of the check     |

### Pricing / Cost estimation

Pay-per-event: **$0.001 per `url-checked`** (definitive verdicts only — rate-limited checks and invalid inputs are not charged) and **$0.002 per `alert-triggered`** (Monitor mode change alerts). Checking 1,000 URLs ≈ **$1** in verdicts plus a small platform fee per run. Example: a 500-URL daily monitor with a handful of changes costs about $0.50/day.

### Tips / Advanced options

- **Monitor scheduling**: use an Apify **Schedule** (`@daily` / cron) with the same `monitorName` — state persists in a named key-value store across runs.
- **Fragile targets**: raise `delayMs` and lower `maxConcurrency` if a server returns `rate_limited` (429). 429 rows are informational and free.
- **Redirect QA after migration**: keep `followRedirects: true` and read `redirectChain` to spot chains longer than 2–3 hops.
- **Large lists**: the hard cap is 10,000 URLs per run — split bigger crawls into batches for predictable pricing.

### For AI Agents & LLM Apps

Purpose: returns per-URL health verdicts (HTTP status, classification, redirect chain, response time, optional SSL expiry) for a list of URLs.

Minimal input:

```json
{ "urls": ["https://example.com"] }
```

Monitor mode variant:

```json
{
    "urls": ["https://example.com"],
    "mode": "monitor",
    "monitorName": "my-site",
    "webhookUrl": "https://hooks.slack.com/..."
}
```

Output fields: `url, finalUrl, status, ok, classification, redirectCount, redirectChain, responseMs, sslValidTo, sslDaysLeft, previousClassification, changed, error, checkedAt`.

Behaviors an agent should know:

- `urls` also accepts a single newline/comma-separated string; scheme-less entries get `https://` prepended; exact duplicates are removed.
- Only definitive verdicts are billed — `rate_limited`, `invalid` and `skipped` rows are free and never overwrite Monitor state.
- `mode: "monitor"` diffs against the previous run stored under `monitorName`; first run has no baseline (`previousClassification: null`).
- `includeSsl` performs one TLS handshake per unique host, not per URL.
- Hard cap: 10,000 URLs per run; exceeding it exits without checking.
- Billing: pay-per-event (`url-checked` $0.001, `alert-triggered` $0.002) plus a small per-run platform fee.

### Related Actors

- [Broken Link Checker](https://apify.com/khadinakbar/broken-link-checker) — link checker with similar per-link verdicts
- [Broken Link Checker (parseforge)](https://apify.com/parseforge/broken-link-checker) — crawl-oriented broken link finder
- [Website Status Checker](https://apify.com/benthepythondev/website-status-checker) — bulk HTTP status checks
- [Redirect Chain Tracer](https://apify.com/ninhothedev/redirect-chain-tracer) — deep redirect-chain inspection
- [Roblox Username Checker](https://apify.com/ahmed_jasarevic/roblox-username-checker) — monitor availability changes with the same monitor/webhook pattern

### FAQ

#### Why use this Actor instead of a website uptime SaaS?

Traditional uptime tools charge per monitor per month; this Actor is pay-per-event — you pay only for URLs actually checked ($0.001 each) and only for definitive verdicts. It also returns the full redirect chain and raw classifications instead of a boolean "up/down", which suits SEO audits and migration QA. For SLA-grade incident paging with phone calls, a dedicated incident platform is still the right tool.

#### What are alternatives to this Actor?

- Crawl-based SEO crawlers (Screaming Frog, Sitebulb) — desktop software, limited to what one machine can crawl.
- SaaS uptime monitors (UptimeRobot, Better Stack, Pingdom) — per-monitor pricing, boolean up/down.
- Community Apify Actors: [khadinakbar/broken-link-checker](https://apify.com/khadinakbar/broken-link-checker), [parseforge/broken-link-checker](https://apify.com/parseforge/broken-link-checker), [benthepythondev/website-status-checker](https://apify.com/benthepythondev/website-status-checker).

#### How can I check 10,000 URLs for broken links?

Paste them into `urls` (array or one-per-line string), leave `mode: "check"` and run. The Actor checks with configurable concurrency and returns one row per URL. Lists larger than 10,000 URLs should be split into batches.

#### How can I monitor my website for downtime or broken links?

Set `mode: "monitor"`, pick a stable `monitorName`, optionally add `webhookUrl`, and create an Apify **Schedule** (e.g. `@daily`). Each run compares current state to the previous run and emits `changed` rows plus webhook alerts only when health changes.

#### Does the Actor follow redirects?

Yes — by default it follows up to 10 hops manually and records the full chain (`redirectChain`, `redirectCount`). Set `followRedirects: false` to inspect the raw first response (e.g. to audit 301/302 behavior).

#### Does it check SSL certificate expiry?

Enable `includeSsl` and the Actor reads each unique HTTPS host's certificate: `sslValidTo` (ISO date) and `sslDaysLeft`. This is one TLS handshake per host, so it stays cheap on large lists.

#### What does "rate limited" mean in the results?

`rate_limited` means the target answered HTTP 429. It is **not** treated as a broken link, is never charged, and never overwrites Monitor state — re-run those URLs later or lower `maxConcurrency` / raise `delayMs`.

### Legal & Compliance Disclaimer

This Actor is an independent tool and is not affiliated with, endorsed by or sponsored by Apify, Google, Slack, Zapier or any website you point it at. It performs read-only HTTP(S) GET requests against URLs you supply — it does not bypass logins, paywalls or CAPTCHAs, and it does not crawl pages unless you submit them directly. You are responsible for ensuring you have permission to request those URLs and for complying with their Terms of Service, `robots.txt` and applicable data-protection law when you use the results.

### Support

Found a bug or need a custom field? Open an issue in the [Issues tab](https://apify.com/ahmed_jasarevic/bulk-url-checker/issues) or start a conversation via the **Start a conversation** button on this page.

### SEO Keywords

bulk url checker, broken link checker, link checker tool, bulk link checker, check url status, http status checker, website uptime monitor, broken link checker api, bulk http status check, redirect checker, redirect chain checker, url health check, link monitoring, website broken links, ssl expiry checker, certificate expiry monitor, check dead links, bulk website checker, url monitor, website downtime monitor, scrapebox link checker alternative, screaming frog broken links alternative, bulk url health checker api, monitor website for broken links, link checker for seo

# Actor input Schema

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

URLs to health-check, one per line. Up to 10,000 per run. Bare domains are upgraded to https:// automatically. Exact duplicates are removed.

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

Check = one-shot audit. Monitor = compare against the previous run and alert only on health changes (pair with an Apify Schedule).

## `monitorName` (type: `string`):

Namespaces saved state in Monitor mode so multiple monitors don't collide. Use the same name on every scheduled run of the same URL list.

## `followRedirects` (type: `boolean`):

Follow 3xx responses to the final destination and record the full redirect chain.

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

Maximum redirect hops to follow before flagging the URL as too\_many\_redirects.

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

Per-request timeout. Slow URLs are classified as timeout (and still count as a definitive verdict).

## `includeSsl` (type: `boolean`):

Also read each HTTPS host's TLS certificate expiry (validTo + days left). One handshake per unique host.

## `webhookUrl` (type: `string`):

Optional. In Monitor mode a JSON POST is sent here for every health change (e.g. ok → broken\_4xx). Wire it to Slack, n8n, Zapier or your own app.

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

How many URL checks run in parallel.

## `delayMs` (type: `integer`):

Optional pause after each check — raise it for fragile targets that throttle aggressive clients.

## Actor input object example

```json
{
  "urls": [
    "https://example.com",
    "https://example.com/pricing",
    "https://example.org"
  ],
  "mode": "check",
  "monitorName": "default",
  "followRedirects": true,
  "maxRedirects": 10,
  "timeoutSecs": 20,
  "includeSsl": false,
  "maxConcurrency": 10,
  "delayMs": 0
}
```

# Actor output Schema

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

No description

## `run` (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://example.com",
        "https://example.com/pricing",
        "https://example.org"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ahmed_jasarevic/bulk-url-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 = { "urls": [
        "https://example.com",
        "https://example.com/pricing",
        "https://example.org",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("ahmed_jasarevic/bulk-url-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 '{
  "urls": [
    "https://example.com",
    "https://example.com/pricing",
    "https://example.org"
  ]
}' |
apify call ahmed_jasarevic/bulk-url-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ahmed_jasarevic/bulk-url-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/5zzisc6X0YcF3aTVV/builds/Ia8ozjuRujTPppQ4e/openapi.json
