Bulk URL Status Checker - HTTP Status & 301 Redirect Checker avatar

Bulk URL Status Checker - HTTP Status & 301 Redirect Checker

Pricing

from $0.80 / 1,000 checked urls

Go to Apify Store
Bulk URL Status Checker - HTTP Status & 301 Redirect Checker

Bulk URL Status Checker - HTTP Status & 301 Redirect Checker

Bulk HTTP status code and redirect chain checker: paste up to 100,000 URLs, a Google Sheet, CSV or sitemap to check URL status: status code, every 301/302 hop, final URL and why a 404, DNS or SSL check failed. Validate a migration redirect map. $1 per 1,000 URLs; timeouts free.

Pricing

from $0.80 / 1,000 checked urls

Rating

0.0

(0)

Developer

Yukai Lin

Yukai Lin

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

3

Monthly active users

10 minutes ago

Last modified

Share

What does Bulk URL Status Checker do?

Bulk URL Status Checker is an HTTP status code and 301 redirect checker for URL lists: give it up to 100,000 URLs and get one row per URL with the status code, every redirect hop, the final URL and the reason when a link fails (404, server error, DNS error, expired SSL certificate, timeout). $1 per 1,000 URLs, no start fee.

You giveYou get (one row per URL)Price
URLs: a list, a column pasted from a spreadsheet, a Google Sheets link, a .txt/.csv file or an XML sitemapstatus (200, 301, 404, 500…), statusClass (ok, broken, server-error, error…), redirectPath (the whole chain in one CSV cell: 301 http://a.com -> 200 https://a.com/), finalUrl, error reason, SSL days left, canonical and indexable$0.001 per URL
A redirect map: old URL, expected new URL per line, or a link to the CSV / Google Sheet (Shopify / WordPress exports work). old URL, 410 for removed pagesmatchesExpected and issue: wrong-target, chain, 302-not-301, https-downgrade, not-redirected, target-broken, still-live$0.001 per old URL

Free: invalid lines, duplicates and URLs that never answer. Results export to CSV, Excel or JSON.

Redirect map example: paste the CSV into Redirect map, set Redirect base URL to the old site, and each line comes back with a verdict (real results, September 2026):

Redirect from,Redirect to → result
/download/,/downloads/ → matchesExpected: true, issue: null
/about/,/about/apps/ → matchesExpected: false, issue: "not-redirected"

Use it as a bulk URL status checker, HTTP status code checker, 301 redirect checker, redirect chain checker, 404 checker or for site migration QA.

  • 🔗 Full redirect chains: every hop with its status (301, 302, 307, 308) and Location header, redirect loops, and http → https upgrades
  • 🧭 Redirect map check (migration QA): old URL + expected new URL per line, or a link to your redirect CSV / Google Sheet (the Old / New columns are found by their header); each result says matchesExpected and the issue: wrong-target, chain, 302-not-301, https-downgrade, not-redirected or target-broken. Pages you removed on purpose: write old URL, 410 and they pass when they answer 404 or 410
  • 📥 Many ways to give URLs: a list, pasted text (any separator), uploaded or linked .txt/.csv files, a Google Sheets link (the normal share link works, no "publish to web" needed), Google Drive or Dropbox file links, or an XML sitemap (indexes and .gz followed)
  • ❌ Clear error reasons: DNS: domain not found, TLS: certificate expired, TLS: self-signed certificate, connection refused, timeout, redirect loop, redirect to a malformed address instead of a bare "failed". Certificates are never ignored: a bad certificate is reported, not skipped
  • 🧠 Smart requests: tries a light HEAD request first and confirms with GET when a server rejects HEAD, so you get fewer false 404s and 405s
  • 🍪 Handles cookie redirects (sites that set a cookie and redirect back to the same page) without reporting a false loop
  • 🔁 Re-checks timeouts once more with a longer timeout, for free, so busy servers are not reported as dead; a URL that still never answers is not charged
  • 🚦 Fast on big lists, polite to each site: up to 40 URLs at a time but at most 8 on one site, connections are reused, and a site that answers 429 Too Many Requests gets fewer parallel requests instead of a column of 429 rows
  • 🏷️ Status classes you can filter on: ok, redirect, broken, server-error, restricted, error, invalid
  • 🔒 SSL certificate expiry: for https URLs, sslDaysLeft, sslValidTo, sslIssuer and sslValid, with sslExpiring when fewer than 14 days are left (configurable). One check per host, no extra charge
  • 🧾 Canonical, meta robots and title (on by default, free): indexable, canonicalMatches and notIndexableReason for final 200 HTML pages; in a redirect map, a new page whose canonical is not the expected URL gets the canonical-mismatch issue
  • 📈 Monitoring mode: compare with the previous run and see which URLs broke, recovered or started redirecting somewhere else
  • ⚙️ Control: follow redirects or stop at the first answer, max redirects, custom User-Agent, HEAD/GET/auto
  • ⚡ Fast and cheap: thousands of URLs per minute, $1 per 1,000 URLs

Who is it for?

  • Site migrations: check that every old URL redirects to the right new page with a single 301, straight from your redirect spreadsheet
  • SEO: find 404s, redirect chains and http links in a list you exported from a sitemap, Search Console or a crawler
  • Link lists and directories: clean up outdated links in bookmarks, resource pages, spreadsheets or a database
  • Monitoring: run it on a schedule with Compare with the previous run and get only the URLs whose status or final URL changed, plus SSL certificates about to expire

How much does it cost?

EventPrice
Checked URL$1 / 1,000 URLs

Example: a 10,000-URL migration check costs 10,000 × $0.001 = $10.00 on the Free plan.

No start fee. Higher Apify plans get volume discounts (see the Pricing tab). Invalid URLs (for example not a url or ftp://…) are reported but not charged, and neither are URLs that never answer (timeouts, dropped connections).

Control your cost

  • Charged: one Checked URL event per URL that got an answer, including a negative one: a 404, a redirect loop, a DNS error, a refused connection or an expired certificate is a result (the URL is dead), so it is charged (charged: true).
  • Free (charged: false): invalid lines such as not a url, Acme Inc or localhost (statusClass: "invalid"), a URL list file or sitemap that could not be read (one invalid row with the reason), relative redirect-map paths without a base URL, duplicate URLs (checked once; the row lists every original line in inputs; EXAMPLE.com, example.com, https://example.com/ and a #fragment count as the same URL) and URLs that never answer.
  • Timeouts are re-checked, and free if they stay silent: a URL that fails to connect (timeout, connection dropped) is checked once more with a timeout of at least 30 seconds, alongside the remaining URLs and at most 2 at a time per site. If the second try answers, the URL is charged once; if it still does not answer, the row says so (charged: false) and SUMMARY.unansweredNotCharged counts them.
  • Input mistakes never fail the run: an empty input, a list of invalid lines or more than 100,000 URLs ends with a free invalid row that explains what to fix and a normal exit (nothing checked, nothing charged).
  • Before it starts, the run logs its plan (number of URLs × price = the most it can cost) and compares it with your maximum charge per run (run options). It is saved as costPlan in SUMMARY.
  • When the maximum is reached, the run stops: the status message says so, SUMMARY.status is LIMIT_REACHED and SUMMARY.notProcessed has the number of URLs not checked and the first 100 of them.
  • If Apify restarts the run (server migration or Resurrect), items already finished are skipped and not charged again (SUMMARY.resumedSkipped).
  • HTTP 429 (Too Many Requests): the URL is checked once more after the wait the server asks for in Retry-After (at most 10 seconds; 2 seconds when it gives none), and the result shows rateLimited. A URL that still answers 429 is reported as restricted with a note. The check is charged once, retry included.

Price comparison (checked September 2026)

ActorPrice per 1,000 URLs (free plan)Redirect map (expected target) check
Bulk URL Status & Redirect Checker (this Actor)$1, no start feeYes
automation-lab/bulk-url-status-checker$0.08 + $0.005 start feeNo
logiover/bulk-url-status-checker$2.50No
automation-lab/http-status-checker$0.575 + $0.035 start feeNo
onescales/simple-http-status-code-checker$300 on the free plan ($10 on Bronze)No

automation-lab/bulk-url-status-checker is much cheaper for large lists and also offers sitemap, text and HTML-signal inputs. Choose this Actor when you need the redirect map check with issue types, cookie-redirect handling, free re-checks of timeouts, SSL expiry and run-to-run comparison. For small batches the start fees matter: 10 URLs cost $0.01 here and about $0.041 with automation-lab/http-status-checker.

vs Apify Sitemap Extractor: it also checks the status of sitemap URLs ($0.50 per 1,000 on the free plan, $0.10 on Gold), but it has no redirect chains, redirect map check or error reasons.

How to use it

  1. Paste your URLs, one per line (bare domains like example.com are fine), or use Paste URLs as text, URL list files or Sitemap URL.
  2. For a migration, fill in Redirect map with one old URL, expected new URL pair per line. Paths only (/old,/new, as exported by Shopify or WordPress)? Also fill in Redirect base URL.
  3. Optional: turn on Only report problems to skip clean 200 responses in the output. Read canonical, meta robots and title (indexability) is on by default; turn it off for the fastest status-only check.
  4. Click Start and export the results as CSV, Excel or JSON.

How to check a list of URLs for 404s and redirects in bulk

  1. Copy the URL column from your spreadsheet (or export from Search Console, a CMS or a crawler) and paste it into Paste URLs as text; any separator works. Or share the sheet as "Anyone with the link" and put its link into URL list files (or URLs): every URL in the tab is checked.
  2. Turn on Only report problems so the output lists only 404s, server errors, redirects and dead hosts.
  3. Start the run. Sort by statusClass: broken (4xx), server-error (5xx), error (DNS, SSL, timeout, loop; the reason is in error). Redirected URLs have redirects above 0, every hop in redirectChain and the same chain as one text cell in redirectPath (handy in CSV and Google Sheets). Each row keeps your original line in input, so you can match it back to the sheet.

How to check 301 redirects after a site migration

  1. Export the redirect list (the URL redirects page of the Shopify admin and WordPress redirect plugins export it as CSV) or write old URL, new URL pairs. Quoted CSV cells are fine. For pages you deleted on purpose, write old URL, 410 (or 404 / gone).
  2. Paste it into Redirect map, header row included, or put the link of the CSV or Google Sheet into Redirect map file (columns such as Old URL / New URL or Redirect from / Redirect to are found by their header; otherwise the first two columns are used). If the old URLs are paths only (/old-page), set Redirect base URL to the old site, e.g. https://shop.com.
  3. Start the run. SUMMARY.redirectMap counts passed and wrong redirects; filter the rows on matchesExpected: false or issue to get the fix list (wrong-target, not-redirected, 302-not-301, chain, https-downgrade, target-broken, canonical-mismatch; for removed pages still-live, redirected, wrong-status).

How to find broken URLs and redirect chains in a sitemap

  1. Put the sitemap address (e.g. https://example.com/sitemap.xml) into Sitemap URL; sitemap indexes and .gz files are followed.
  2. Turn on Only report problems: a sitemap should list only final 200 pages, so every row in the output (redirect, 404, not indexable because of noindex or another canonical) is a sitemap entry to fix.
  3. To keep watching it, turn on Compare with the previous run and schedule the task weekly (see Monitoring below).

Input example

{
"urls": [
"http://python.org",
"https://github.com/apify/crawlee",
"https://example.com/this-page-does-not-exist",
"https://expired.badssl.com/"
],
"onlyProblems": false,
"method": "auto",
"timeoutSecs": 20
}

Output example

Every row starts with input / inputUrl (the line exactly as you gave it, e.g. python.org) and inputIndex (its position after duplicates are merged), and ends with charged, so results map back to your spreadsheet. url is the address that was checked (bare domains get https://).

Real results from the input above. A redirect from http to https:

{
"url": "http://python.org",
"finalUrl": "https://www.python.org/",
"status": 200,
"statusClass": "ok",
"redirects": 1,
"statusText": "OK",
"redirectChain": [
{ "url": "http://python.org", "status": 301, "location": "https://www.python.org/" },
{ "url": "https://www.python.org/", "status": 200 }
],
"httpsUpgrade": true,
"contentType": "text/html; charset=utf-8",
"contentLength": 11774,
"server": "nginx",
"xRobotsTag": null,
"responseMs": 341,
"error": null
}

A site with an expired certificate:

{
"url": "https://expired.badssl.com/",
"finalUrl": "https://expired.badssl.com/",
"status": null,
"statusClass": "error",
"redirects": 0,
"redirectChain": [],
"error": "TLS: certificate expired",
"errorType": "network",
"responseMs": 86
}

SSL and indexability (real results, September 2026)

With Read canonical, meta robots and title on ("includeHtmlSignals": true, the default; set it to false for the fastest status-only check), a page whose canonical points to another URL is reported as not indexable:

{
"url": "https://www.apple.com/iphone?x=1",
"finalUrl": "https://www.apple.com/iphone/?x=1",
"status": 200,
"sslHost": "www.apple.com",
"sslValid": true,
"sslValidTo": "2026-12-16T18:31:25.000Z",
"sslDaysLeft": 78,
"sslIssuer": "Apple Inc.",
"sslExpiring": false,
"sslExpired": false,
"title": "iPhone - Apple",
"canonical": "https://www.apple.com/iphone/",
"metaRobots": null,
"canonicalUrl": "https://www.apple.com/iphone/",
"canonicalMatches": false,
"indexable": false,
"notIndexableReason": "canonical points elsewhere"
}

The expired test site gets "sslDaysLeft": -4188, "sslValid": false, "sslExpired": true, "sslError": "expired", "sslIssuer": "COMODO CA Limited". SSL fields are added by default (turn off Check SSL certificates to skip them); sslError also reports self-signed, wrong host or incomplete chain.

Redirect map (migration check)

Input: one pair per line in Redirect map (comma, tab, space or -> between the two URLs; a CSV header row is skipped). Via the API you can also pass objects { "from": "...", "to": "..." }.

{
"redirectMap": [
"http://python.org, https://www.python.org/",
"https://www.python.org/download/, https://www.python.org/downloads/source/",
"http://stripe.com/docs, https://docs.stripe.com/",
"https://www.python.org/nothing-here/, https://www.python.org/"
]
}

Real results (September 2026, shortened):

[
{ "url": "http://python.org", "finalUrl": "https://www.python.org/", "redirects": 1, "expectedUrl": "https://www.python.org/", "matchesExpected": true, "issue": null },
{ "url": "https://www.python.org/download/", "finalUrl": "https://www.python.org/downloads/", "expectedUrl": "https://www.python.org/downloads/source/", "matchesExpected": false, "issue": "wrong-target" },
{ "url": "http://stripe.com/docs", "finalUrl": "https://docs.stripe.com/", "redirects": 2, "expectedUrl": "https://docs.stripe.com/", "matchesExpected": true, "issue": "chain" },
{ "url": "https://www.python.org/nothing-here/", "status": 404, "expectedUrl": "https://www.python.org/", "matchesExpected": false, "issue": "not-redirected" }
]
issueMeaning
wrong-targetRedirects, but not to the expected URL
not-redirectedThe old URL does not redirect at all
target-brokenRedirects to a page that returns an error
chainReaches the right page through more than one redirect
302-not-301The first redirect is temporary (302/303/307); use 301 or 308 for moved pages
https-downgradeA redirect in the chain goes from https to http
canonical-mismatchOnly with Read canonical, meta robots and title: the right page is reached, but its canonical tag points to another URL (real example: http://docs.python.org/3/ → canonical https://docs.python.org/3/index.html)
still-live / redirected / wrong-statusRemoved pages (old URL, 410): the page still answers 2xx / it redirects instead of answering 404 or 410 / it answers something else (5xx, DNS error). 404 and 410 both pass

issues lists all problems found; issue is the most important one. URLs are compared exactly, except for host letter case and a trailing slash.

Relative paths (Shopify, WordPress exports). Redirect exports often use paths only, such as /download/,/downloads/. Put the old site in Redirect base URL ("redirectBaseUrl": "https://www.python.org") and each path is checked on that site; a relative target is put on the same site. Without it, the base is taken from the full old URLs in the map when they all share one site; otherwise the relative lines come back as statusClass: "invalid" with the error relative path: set "Redirect base URL"… (not charged) and keep their expectedUrl.

{
"redirectMap": ["Redirect from,Redirect to", "/download/,/downloads/", "/about/,/about/apps/"],
"redirectBaseUrl": "https://www.python.org"
}

Real results (September 2026, shortened):

[
{ "input": "/download/", "url": "https://www.python.org/download/", "finalUrl": "https://www.python.org/downloads/", "expectedUrl": "https://www.python.org/downloads/", "matchesExpected": true, "issue": null },
{ "input": "/about/", "url": "https://www.python.org/about/", "status": 200, "expectedUrl": "https://www.python.org/about/apps/", "matchesExpected": false, "issue": "not-redirected" }
]

Summary

SUMMARY in the key-value store: status (SUCCESS, PARTIAL_RESULTS, FAILED, NO_RESULTS, LIMIT_REACHED), counts by status class, ssl (hosts checked, expiring and invalid hosts), htmlSignals counts when enabled, comparison in monitoring mode, duplicatesMerged, costPlan, notProcessed (only when the run stopped at your limit), redirectBaseUrl / relativePathsWithoutBase when the map has relative paths and, with a redirect map, redirectMap: { "checked": 4, "matches": 2, "mismatches": 2, "passed": 1, "withIssues": 3, "byIssue": { "wrong-target": 1, "chain": 1, "not-redirected": 1 } }.

Monitoring: compare with the previous run

Turn on Compare with the previous run and schedule the Actor (daily or weekly). Each result gets change: new, changed or unchanged; changed URLs also get changeType (broke, recovered, status-changed, new-redirect, redirect-target-changed, redirect-removed), previousStatus and previousFinalUrl. With Only output changes, later runs output only new and changed URLs, so an Apify integration (email, Slack, webhook) only fires when something happened. Runs with the same Monitor name are compared (default: the sitemap or first URL's host).

SUMMARY.comparison from a real second run (one URL added, one removed):

{ "monitorName": "c2-test", "firstRun": false, "new": 1, "changed": 0, "unchanged": 6, "removed": 1, "changedUrls": [],
"removedUrls": [{ "url": "https://www.apple.com/iphone?x=1", "previousStatus": 200, "previousFinalUrl": "https://www.apple.com/iphone/?x=1" }] }

The first run of a monitor is the baseline (firstRun: true, every URL is new). URLs are checked and charged the same way in every run.

Status classes

statusClassMeaning
ok2xx after following redirects
broken4xx such as 404 Not Found or 410 Gone
server-error5xx
restricted401, 403, 429, or a site known to block automated checks (LinkedIn, X/Twitter, Facebook…). Usually works in a normal browser
errorNo HTTP answer: DNS, SSL, refused connection, timeout, redirect loop
invalidNot a valid http(s) URL (not charged)

Use with AI agents (MCP)

Connect Apify's MCP server (https://mcp.apify.com?tools=tidytools/bulk-url-status-checker) to Claude, Cursor or any MCP client, then ask e.g. "Check these 200 old URLs against our redirect spreadsheet and list the ones that do not land on the right page."

{ "urls": ["http://python.org", "https://example.com/this-page-does-not-exist"] }

Unreachable URLs carry an errorType (network, timeout…); invalid URLs are not charged.

Use it from code and integrations

Run it from your own code with the Apify API. This call waits for the run and returns the results as JSON (replace YOUR_TOKEN with your Apify API token):

curl -X POST "https://api.apify.com/v2/acts/tidytools~bulk-url-status-checker/run-sync-get-dataset-items?token=YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"urls":["http://python.org","https://github.com/apify/crawlee","https://example.com/this-page-does-not-exist","https://expired.badssl.com/"],"includeHtmlSignals":true}'

The synchronous endpoint waits up to 5 minutes. For bigger runs, start the run with POST https://api.apify.com/v2/acts/tidytools~bulk-url-status-checker/runs and read the dataset when it finishes, or use the apify-client package for JavaScript or Python.

Schedules and integrations: run it daily or weekly with Apify Schedules, get a webhook when a run finishes, or send the results to Zapier, Make, n8n, Google Sheets, Slack and other apps with Apify integrations. Results can be exported as JSON, CSV, Excel or XML.

Tips

  • To check every link on a website rather than a list, use Broken Link Checker (by TidyTools), which crawls the site and tells you which page each broken link is on.
  • Need the URL list first? Sitemap URL Extractor (by TidyTools) gets all URLs of a site from its sitemaps.
  • Use HEAD only for the fastest checks on servers you control, or GET only for servers that answer HEAD incorrectly.
  • Checking thousands of URLs on one site of your own? Raise Parallel requests per site (maxConcurrencyPerHost, default 8) to go faster; lower it for a small or rate-limited site.
  • Turn off Follow redirects to see only the first answer (status and location) of each URL.

Limitations

  • Only the exact URLs you give are requested (one request each, plus the redirects it follows), with the User-Agent TidyToolsLinkCheck/1.0 (+https://apify.com/tidytools) unless you set your own. It does not crawl, so it does not read robots.txt.
  • Private and local addresses (localhost, 10.x.x.x, names without a dot) cannot be reached from Apify's servers; names without a dot are reported as invalid (not charged).
  • Requests come from Apify's servers. Some sites block data-center traffic or show a bot challenge; those URLs are reported as restricted or error even if they work for you.
  • Page content is not downloaded, except the first 256 KB of final 200 HTML pages when Read canonical, meta robots and title is on. Canonical and robots tags added later by JavaScript are not seen.
  • The SSL certificate is read with a direct connection from Apify's servers, not through the proxy.

FAQ

Does the Actor collect usage data? The Actor sends one anonymous usage counter per run (input type, item count range, outcome) to improve the tool; no URLs, content or account data.

Can it check my redirects after a site migration? Yes. Put one "old URL, expected new URL" pair per line in Redirect map (migration check); each result gets matchesExpected and an issue such as wrong-target, chain, 302-not-301, not-redirected or target-broken. For path-only exports from Shopify or WordPress, also fill in Redirect base URL (for relative paths).

Can I give it a Google Sheet? Yes. Share the sheet as "Anyone with the link" (Viewer) and paste its normal link into URL list files or URLs: the tab in the link is read as CSV and every URL in it is checked. A redirect map sheet goes into Redirect map file. A private sheet gives one free invalid row that says how to share it.

Does the redirect checker show the full redirect chain? Yes. Every hop is listed with its status (301, 302, 307, 308) and Location header, plus the final URL, redirect loops, http → https upgrades and https → http downgrades (httpsDowngrade). redirectPath puts the whole chain in one text column for CSV and Excel exports. Turn off Follow redirects to see only the first response, and use Max redirects to set where following stops.

How do I use it as a 404 checker and only see broken URLs? Turn on Only report problems: only URLs that are not a clean 200 (redirects, broken, errors) are output, though all URLs are still checked and charged. 404s get statusClass: "broken"; to crawl every link on a website instead of a list, use Broken Link Checker by TidyTools.

Why does the HTTP status checker report a URL as restricted when it opens in my browser? Requests come from Apify's servers, and some sites block data-center traffic or show a bot challenge, so those URLs are reported as restricted or error even if they work for you. restricted also covers 401, 403, 429 and sites known to block automated checks such as LinkedIn, X/Twitter and Facebook.

Am I charged for dead links and failed checks? A dead link is the answer you asked for, so a 404, a DNS error, a refused connection or an expired or self-signed certificate is charged ($0.001, charged: true). Not charged: invalid input lines, duplicates, and URLs that never answer even after the slower second try (timeout, connection dropped; charged: false). The run's SUMMARY.billing shows what was charged.

What happens when my input is wrong (empty, no valid URLs, unreadable list file)? The run still finishes normally and costs nothing: every bad line or unreadable list or sitemap gets one invalid row with the reason (errorType: "invalid_input"), so you can fix it and run again. Via the API, urls or redirectMap sent as one text block instead of a list is split into lines, and numbers sent as text ("timeoutSecs": "20") are read; an unusable number falls back to the default.

Does it check bad certificates? It reports them and never turns certificate checking off: https://expired.badssl.com/ comes back as TLS: certificate expired, a self-signed one as TLS: self-signed certificate, a wrong host name as TLS: certificate does not match the domain. The SSL fields (sslError, sslDaysLeft) say more.

Are example.com and EXAMPLE.com/ the same URL? Yes: upper/lower case in the host name, a trailing slash on a bare domain and a #fragment do not change the request, so they are checked once and charged once. The row lists every spelling in inputs.

Support

Open an issue in the Issues tab with an example URL. Issues are checked regularly.

Found this useful? A short review on the Store helps other users find it.