# URL Status Checker (`deriverge/url-status-checker`) Actor

Checks the HTTP status of thousands of URLs and lists every redirect hop with its Location, plus the final URL, response time, title, canonical and robots tags. Each failure gets a class such as DNS, timeout, TLS or refused connection.

- **URL**: https://apify.com/deriverge/url-status-checker.md
- **Developed by:** [deriverge s.r.o.](https://apify.com/deriverge) (community)
- **Categories:** SEO tools, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.15 / 1,000 urls

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

## URL Status Checker

URL Status Checker takes a list of URLs and returns one row per URL: the final HTTP status, every redirect hop with its status and `Location`, the final URL, response time, content type and length, page title, canonical URL, meta robots, `X-Robots-Tag` and HSTS. When there is no answer, the row says why: DNS, timeout, TLS, refused or reset connection, or a redirect loop. Paste your URLs into **URLs** and click **Start**. Pages behind bot protection are marked `restricted` because their real status cannot be read. It costs $0.30 per 1,000 URLs on the Free plan ($0.15 on Business), and the $5 of monthly credit in Apify's Free plan covers about 16,000 URLs.

### What data does it return?

| Field | Description |
|---|---|
| `url` | The URL as checked. A line without a scheme gets `https://`, and the `#fragment` is dropped because servers never see it. |
| `result` | `ok` (final answer 2xx), `broken` (4xx, 5xx or no answer), `restricted` (401, 407, bot protection or rate limit), `redirect` (a 3xx with **Follow redirects** off) or `not checked`. |
| `status`, `statusText` | Status code and reason phrase of the final answer, such as `404` and `Not Found`. Empty when no answer came. |
| `finalUrl` | Where the URL ends up after all redirects, such as `https://www.wikipedia.org/` for `http://wikipedia.org`. |
| `redirectCount`, `redirectChain` | Number of redirects and each hop in order: `url`, `status` (301, 302, 303, 307 or 308) and `location`. |
| `responseTimeMs` | Milliseconds from the request for the final URL to its response headers (time to first byte). |
| `totalTimeMs` | Milliseconds for the whole check, redirects included. |
| `contentType`, `contentLength` | `Content-Type` and `Content-Length` headers of the final answer. Many compressed pages send no length, so it is often empty. |
| `title` | Text of the page's `<title>` tag. |
| `canonical` | URL from `<link rel="canonical">`, made absolute. |
| `metaRobots`, `xRobotsTag` | The `<meta name="robots">` tag and the `X-Robots-Tag` header, such as `noindex, follow`. |
| `isHttps`, `hsts` | Whether the final URL uses HTTPS, and its `Strict-Transport-Security` header. |
| `server` | The `Server` header, such as `nginx` or `cloudflare`. |
| `method` | `HEAD` or `GET`, the method of the final request. |
| `errorClass`, `error` | Why there is no usable answer: `dns`, `timeout`, `tls`, `connection_refused`, `connection_reset`, `network`, `invalid_response`, `redirect_loop`, `too_many_redirects`, `invalid_url`, `bot_protection`, `rate_limited` or `not_requested`, with a sentence such as `The TLS certificate has expired.` |
| `change` | With a watch name: `new URL`, `newly broken`, `fixed`, `changed` or `unchanged` since the last run. |
| `checkedAt` | When the URL was checked, as an ISO 8601 UTC timestamp. |

### How to check the status of many URLs

1. Paste the URLs into **URLs**, one per line. A spreadsheet column pasted as is works too: header lines and anything that is not a URL are skipped and counted in `SUMMARY`.
2. Leave **Read title, canonical and meta robots** on for an SEO check. Turn it off when you only need status codes and redirects: each URL then gets a `HEAD` request, with `GET` only when the server refuses `HEAD`.
3. Turn off **Follow redirects** if you want the first answer of each URL, for example to audit a redirect map: a moved URL then shows its 301 and where it points.
4. Set **Timeout per URL** and the two concurrency limits if needed, and a **User agent** if your firewall only lets certain clients through.
5. Click **Start**. Rows appear in the **Output** tab in the order of your list, with views for redirects, SEO tags and errors, and can be exported as CSV, Excel or JSON. The `SUMMARY` record in the key-value store counts results by status code and error class and gives the median and 95th percentile response time.

Over the API, an input looks like this:

```json
{
  "urls": ["https://example.com/", "http://example.com/old-page"],
  "pageDetails": true,
  "followRedirects": true,
  "timeoutSecs": 15,
  "watchName": "shop-urls"
}
```

### Example output

One row from a run on 11 October 2026, for a URL that redirects from HTTP to HTTPS:

```json
{
  "url": "http://github.com/",
  "result": "ok",
  "status": 200,
  "statusText": "OK",
  "finalUrl": "https://github.com/",
  "redirectCount": 1,
  "redirectChain": [
    {
      "url": "http://github.com/",
      "status": 301,
      "location": "https://github.com/"
    }
  ],
  "responseTimeMs": 41,
  "totalTimeMs": 75,
  "contentType": "text/html; charset=utf-8",
  "contentLength": null,
  "title": "GitHub · Change is constant. GitHub keeps you ahead. · GitHub · GitHub",
  "canonical": "https://github.com/",
  "metaRobots": null,
  "xRobotsTag": null,
  "isHttps": true,
  "hsts": "max-age=31536000; includeSubdomains; preload",
  "server": "github.com",
  "method": "GET",
  "errorClass": null,
  "error": null,
  "change": null,
  "checkedAt": "2026-10-11T15:26:28.812Z"
}
```

### How much does it cost to check URL status?

| | Free plan | Starter | Scale | Business |
|---|---|---|---|---|
| 1,000 URLs | $0.30 | $0.24 | $0.195 | $0.15 |

You pay only for the events in the table. There is no start fee, and compute time and proxies are included.

A list of 10,000 URLs costs $3.00 on the Free plan and $1.50 on Business. A URL repeated in the list is checked and charged once. Lines that are not URLs, and URLs that were not requested because their host had already answered with a bot protection challenge, are not charged.

### Limits

- Speed depends on the hosts. On 11 October 2026 a list of 1,000 URLs from Hacker News stories, spread over 656 hosts, took 50 seconds with the default settings; slow hosts and timeouts take most of that time. 390 URLs on one fast host took 2 seconds.
- Each host gets at most 4 parallel requests by default (**Requests at once per host**, up to 10), so a long list from one site runs at that pace.
- A host that answers 429 Too Many Requests gets a pause (its `Retry-After`, between 2 and 60 seconds) and then one request at a time. A URL is tried 3 times; after the fifth 429 from one host, its remaining URLs are returned as `not checked`.
- A 502, 503 or 504 slows that host to one request per second and the URL is tried once more before the status counts.
- When a host answers with a bot protection challenge (Cloudflare, DataDome, Akamai, Imperva, PerimeterX, AWS WAF, Anubis, LinkedIn's 999 and similar block pages), the row is `restricted`, the actor does not try to get around it, and the host's remaining URLs are `not checked`.
- Up to 10 redirects are followed. Cookies set during a redirect chain are sent on the next hops, as a browser would, so cookie-based redirects do not look like loops.
- The title, canonical and meta robots come from the `<head>` of HTML pages with a 2xx answer, read up to 1 MB. Tags added later by JavaScript are not seen, because pages are not rendered.
- Certificates are verified. An expired, self-signed or mismatched certificate is reported as `tls`, which is what a visitor's browser would complain about too.
- Each URL in your list gets one check (plus its redirects). robots.txt is not read; use Broken Link Checker to crawl a whole site with robots.txt respected.

### Watching URLs on a schedule

Give the run a **Watch name**, or save the input as a task, and schedule it daily or hourly. Each row then has a `change` value compared with the previous run of that watch, and a `CHANGES` record lists:

- `newlyBroken`: URLs that worked last time and fail now, with the old and new status.
- `fixed`: URLs that failed last time and work now.
- `changed`: URLs whose status, final URL or error class changed in another way, such as a new redirect.
- `newUrls`: how many URLs were not in the previous run.

URLs from earlier runs that are missing from today's list stay in the snapshot for 90 days, so a watch can rotate through several lists.

### FAQ

#### Is it legal to check the status of URLs?

The actor sends the same requests a browser sends when someone opens the URL, one check per URL, and stores only HTTP metadata and the head tags of each page. It does not log in, solve challenges or read page content beyond the `<head>`. You are responsible for the URLs you check and for following the terms of the sites they belong to.

#### Why does a URL work in my browser but show 403 here?

Some sites block requests from data centers or without browser cookies. When the answer looks like a bot protection page, the row says `restricted` with the name of the service; a plain 403 stays `broken`. Setting **User agent** to what your own firewall allows helps for your own sites.

#### Does HEAD give the same result as GET?

Not always, which is why a URL is requested again with `GET` whenever `HEAD` returns 400 or more or drops the connection. The `method` field shows which one gave the final answer.

### Related scrapers

- [Broken Link Checker](https://apify.com/deriverge/broken-link-checker)
- [Tech Stack Detector](https://apify.com/deriverge/tech-stack-detector)
- [Domain WHOIS and DNS Lookup](https://apify.com/deriverge/domain-whois-dns-lookup)

### Support

This actor is built and maintained by deriverge s.r.o., a software company based in the Czech Republic. If a run fails or a field you need is missing, please open an issue in the **Issues** tab or write to us at info@deriverge.com. We respond in English and Czech. Runs can be scheduled in Apify Console or started from the **API** tab, which has examples for Python, JavaScript and cURL and works with Make, Zapier, n8n and the Apify MCP server. If the actor saves you time, a short review helps other people find it.

# Changelog

This Actor's version history is a separate document: https://apify.com/deriverge/url-status-checker/changelog.md

# Actor input Schema

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

One URL per line, up to tens of thousands. Addresses without http:// or https:// get https://. Duplicates are checked once, and lines that are not URLs (a CSV header, an email address) are skipped and not charged.

## `pageDetails` (type: `boolean`):

On: every URL is requested with GET and the head of HTML pages is read for the title, canonical URL and meta robots tag. Off: a HEAD request only, with GET when the server refuses HEAD; faster and lighter, and the three tags stay empty.

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

On: up to 10 redirects are followed and every hop is listed with its status and Location. Off: the first answer is returned, so a redirecting URL shows its 301 or 302 and where it points.

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

How long one URL may take, redirects included, before it is reported with the error class timeout.

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

How many URLs are checked in parallel across all hosts. Each host still gets at most Requests at once per host.

## `maxConcurrencyPerHost` (type: `integer`):

The most parallel requests one host gets. A list of 5,000 URLs from one site runs at this pace, so the site is never flooded. A host that answers 429 is slowed to one request at a time.

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

Custom User-Agent header, for example to match what your firewall allows. Empty means a current desktop Chrome user agent.

## `watchName` (type: `string`):

Name of the snapshot used to compare runs, for example "shop-urls". With a watch name (or a saved task, which gets one automatically) each row says whether the URL is newly broken, fixed, changed or unchanged since the last run, and a CHANGES record lists them.

## Actor input object example

```json
{
  "urls": [
    "https://apify.com/",
    "http://github.com",
    "http://wikipedia.org",
    "https://www.python.org/",
    "https://developer.mozilla.org/",
    "https://www.iana.org/help/example-domains",
    "https://www.bbc.com/news",
    "https://github.com/apify/this-repository-does-not-exist",
    "https://expired.badssl.com/",
    "https://www.example.invalid/"
  ],
  "pageDetails": true,
  "followRedirects": true,
  "timeoutSecs": 15,
  "maxConcurrency": 50,
  "maxConcurrencyPerHost": 4
}
```

# Actor output Schema

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

One row per URL with status, redirect chain, final URL, response time, content type and length, title, canonical, robots tags, HTTPS and HSTS, and the error class.

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

Counts by result, status group, status code and error class, response time median and 95th percentile, skipped lines and hosts that stopped answering.

## `changes` (type: `string`):

With a watch name or a saved task: URLs that are newly broken, fixed or changed since the previous run.

# 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",
        "http://wikipedia.org",
        "https://www.python.org/",
        "https://developer.mozilla.org/",
        "https://www.iana.org/help/example-domains",
        "https://www.bbc.com/news",
        "https://github.com/apify/this-repository-does-not-exist",
        "https://expired.badssl.com/",
        "https://www.example.invalid/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("deriverge/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",
        "http://wikipedia.org",
        "https://www.python.org/",
        "https://developer.mozilla.org/",
        "https://www.iana.org/help/example-domains",
        "https://www.bbc.com/news",
        "https://github.com/apify/this-repository-does-not-exist",
        "https://expired.badssl.com/",
        "https://www.example.invalid/",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("deriverge/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",
    "http://wikipedia.org",
    "https://www.python.org/",
    "https://developer.mozilla.org/",
    "https://www.iana.org/help/example-domains",
    "https://www.bbc.com/news",
    "https://github.com/apify/this-repository-does-not-exist",
    "https://expired.badssl.com/",
    "https://www.example.invalid/"
  ]
}' |
apify call deriverge/url-status-checker --silent --output-dataset

```

## MCP server setup

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