# Bulk URL Status Checker: Redirects, Broken Links, SSL (`larkmoor/bulk-url-status-checker`) Actor

Check thousands of URLs at once: HTTP status, full redirect chain (301/302 hops), response time, content type and size, page title, canonical, noindex (meta robots + X-Robots-Tag) and SSL certificate expiry. Find broken links, redirect loops and expiring certificates. Pay per URL.

- **URL**: https://apify.com/larkmoor/bulk-url-status-checker.md
- **Developed by:** [Larkmoor Data](https://apify.com/larkmoor) (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.06 / 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

## Bulk URL Status Checker: Redirects, Broken Links, SSL

Paste a list of URLs (or upload thousands through the API) and get one clean row per URL: the HTTP status code, every redirect hop, the final URL, response time, content type and size, the page title, the canonical URL, whether the page is set to noindex, and, if you want, the SSL certificate's expiry date and issuer.

Built for SEO audits, site migrations, link monitoring and data cleaning. No browser, no proxy, no API key.

### What you can use it for

- **Site migrations**: confirm every old URL 301-redirects to the right new page, in one hop, with no loops
- **Broken link checks**: find 404s, 410s, 5xx errors, DNS failures and timeouts in a link list, sitemap export or CRM field
- **SEO audits**: spot pages that are noindex by accident (meta robots or X-Robots-Tag), wrong canonicals and slow responses
- **Certificate monitoring**: list every HTTPS host whose certificate is invalid or expires within 30 days
- **Data cleaning**: validate website columns in lead lists before outreach or enrichment

### Why this one

- **Full redirect chain**, hop by hop, with each status code, plus loop detection
- **Status category** you can filter on: `ok`, `ok_after_redirect`, `redirect`, `client_error`, `server_error`, `error`
- **SEO fields in the same row**: title, canonical, noindex (from the page and from the X-Robots-Tag header)
- **SSL expiry and issuer** per host (optional), cached so a host is read once per run
- **"Only return problems" mode**: you pay only for the rows that need attention
- **Fast**: up to 50 parallel requests; servers that refuse HEAD are retried with GET automatically

### Input

| Field | What it does | Default |
|---|---|---|
| `urls` | URLs or bare domains, one per line | required |
| `followRedirects` | Follow and record redirect hops | true |
| `maxRedirects` | Hop limit | 10 |
| `fetchContent` | Read title, canonical and noindex from HTML pages | true |
| `method` | HEAD or GET when content reading is off | HEAD |
| `checkSsl` | Read certificate expiry, days left and issuer | false |
| `onlyProblems` | Return only broken, redirected, noindex or SSL-problem URLs | false |
| `concurrency` | Parallel requests (1–50) | 10 |
| `timeoutSeconds` | Timeout per request | 20 |
| `userAgent` | Custom User-Agent | desktop Chrome |
| `maxResults` | Stop after this many URLs | 10,000 |

Example input:

```json
{
  "urls": ["https://apify.com", "http://github.com", "https://httpbin.org/status/404"],
  "checkSsl": true
}
```

### Output

| Field | Meaning |
|---|---|
| `url`, `finalUrl` | The URL you gave and where it ended up |
| `statusCode`, `statusText`, `ok`, `category` | Final HTTP status and a filterable category |
| `redirectCount`, `redirectChain` | Number of hops and `[{url, statusCode}]` for each |
| `responseTimeMs` | Total time including redirects |
| `contentType`, `contentLength`, `server` | Response headers |
| `title`, `canonical`, `noindex`, `xRobotsTag` | SEO fields (HTML pages) |
| `sslExpiresAt`, `sslDaysLeft`, `sslIssuer` | Certificate facts (when `checkSsl` is on) |
| `error`, `checkedAt` | What went wrong (DNS, timeout, SSL, loop) and when the check ran |

Sample rows from a test run (October 2026):

```json
[
  {"url": "http://github.com", "finalUrl": "https://github.com/", "statusCode": 200, "category": "ok_after_redirect",
   "redirectCount": 1, "redirectChain": [{"url": "http://github.com", "statusCode": 301}, {"url": "https://github.com/", "statusCode": 200}],
   "title": "GitHub · Change is constant. GitHub keeps you ahead. · GitHub · GitHub", "canonical": "https://github.com", "noindex": false,
   "sslDaysLeft": 42, "sslIssuer": "Sectigo Limited"},
  {"url": "https://httpbin.org/status/404", "statusCode": 404, "category": "client_error", "ok": false},
  {"url": "https://expired.badssl.com/", "statusCode": null, "category": "error", "error": "SSL error: certificate has expired",
   "sslExpiresAt": "invalid: certificate has expired"}
]
```

### How much does it cost?

Pay per result: **$1.25 per 1,000 URLs** on the Free plan (a small one-time start fee per run), with automatic discounts on paid Apify plans. Platform compute is included. With "Only return problems" on, URLs that pass are not charged.

Worked examples:

- **A 500-page site migration check**: 500 × $0.00125 = **$0.63**
- **20,000 links from a CRM**, all returned: **$25.00**
- **20,000 links with "Only return problems"**, 1,200 problems found: 1,200 × $0.00125 = **$1.50**

### FAQ

**Why do some sites return 403 here but open in my browser?** Some sites block automated requests. The row still tells you the server answered (it is up). Try a custom `userAgent`, or treat 403 from such sites as "reachable".

**HEAD or GET?** With content reading on (the default) every check is a GET, because the title and noindex live in the page. With it off, HEAD is used and servers that refuse HEAD are retried with GET.

**Does it follow JavaScript or meta-refresh redirects?** No: it follows HTTP redirects (301, 302, 303, 307, 308), which are the ones search engines treat as redirects.

**What counts as a problem in "Only return problems"?** Status 400 or above, a connection, DNS or SSL error, at least one redirect, noindex, or a certificate that is invalid or expires within 30 days.

**Is it legal?** It requests the URLs you give it, once each, like a browser would. Check only sites and links you have a legitimate reason to check, and keep concurrency modest on small sites.

**Can I schedule it?** Yes: save an Apify task with your list and run it daily or weekly; pair "Only return problems" with a webhook or email integration to get alerts.

### Integrations

Run it through the Apify API, on a schedule, or from Make, Zapier, n8n, Google Sheets, webhooks and AI agents (Apify MCP server). Export the dataset as JSON, CSV, Excel, XML or HTML.

### Changelog

- 0.1 (October 2026): first release: status, redirect chain, loop detection, title, canonical, noindex, SSL expiry, problems-only mode.

# Actor input Schema

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

URLs or domains to check, one per line (a missing https:// is added).

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

Follow 301/302/307/308 hops and record each one in `redirectChain`. Default: true.

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

Stop following after this many hops. Default: 10.

## `fetchContent` (type: `boolean`):

Download HTML pages (up to 600 KB) to read the title, canonical URL and meta robots noindex. Turn off for status codes only (faster). Default: true.

## `method` (type: `string`):

Only used when content reading is off. HEAD is lighter; servers that refuse HEAD are retried with GET automatically.

## `checkSsl` (type: `boolean`):

Read the certificate of each HTTPS host: expiry date, days left, issuer. Default: false.

## `onlyProblems` (type: `boolean`):

Return only URLs that are broken, redirected, noindex, or with an SSL certificate that is invalid or expires within 30 days. You pay only for returned rows. Default: false.

## `concurrency` (type: `integer`):

How many URLs are checked at the same time. Default: 10.

## `timeoutSeconds` (type: `integer`):

Default: 20.

## `userAgent` (type: `string`):

Optional custom User-Agent header. Default: a current desktop Chrome string.

## `maxResults` (type: `integer`):

Stop after this many URLs. Default: 10000.

## Actor input object example

```json
{
  "urls": [
    "https://apify.com",
    "http://github.com",
    "https://httpbin.org/status/404"
  ],
  "followRedirects": true,
  "maxRedirects": 10,
  "fetchContent": true,
  "method": "HEAD",
  "checkSsl": false,
  "onlyProblems": false,
  "concurrency": 10,
  "timeoutSeconds": 20,
  "maxResults": 10000
}
```

# Actor output Schema

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

All results as JSON items in the run's default dataset (also downloadable as CSV/Excel from the 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://apify.com",
        "http://github.com",
        "https://httpbin.org/status/404"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("larkmoor/bulk-url-status-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://apify.com",
        "http://github.com",
        "https://httpbin.org/status/404",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("larkmoor/bulk-url-status-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://apify.com",
    "http://github.com",
    "https://httpbin.org/status/404"
  ]
}' |
apify call larkmoor/bulk-url-status-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,larkmoor/bulk-url-status-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/6SCTzhaeCJLGa4LO3/builds/cgbYwN9O51W0L3fIO/openapi.json
