Bulk URL Status Checker - HTTP Status & 301 Redirect Checker
Pricing
from $0.80 / 1,000 checked urls
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
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
3
Monthly active users
10 minutes ago
Last modified
Categories
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 give | You 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 sitemap | status (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 pages | matchesExpected 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
Locationheader, 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
matchesExpectedand the issue:wrong-target,chain,302-not-301,https-downgrade,not-redirectedortarget-broken. Pages you removed on purpose: writeold URL, 410and they pass when they answer 404 or 410 - 📥 Many ways to give URLs: a list, pasted text (any separator), uploaded or linked
.txt/.csvfiles, 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.gzfollowed) - ❌ Clear error reasons:
DNS: domain not found,TLS: certificate expired,TLS: self-signed certificate,connection refused,timeout,redirect loop,redirect to a malformed addressinstead 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 Requestsgets 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,sslIssuerandsslValid, withsslExpiringwhen fewer than 14 days are left (configurable). One check per host, no extra charge - 🧾 Canonical, meta robots and title (on by default, free):
indexable,canonicalMatchesandnotIndexableReasonfor final 200 HTML pages; in a redirect map, a new page whose canonical is not the expected URL gets thecanonical-mismatchissue - 📈 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?
| Event | Price |
|---|---|
| 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 URLevent 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 asnot a url,Acme Incorlocalhost(statusClass: "invalid"), a URL list file or sitemap that could not be read (oneinvalidrow with the reason), relative redirect-map paths without a base URL, duplicate URLs (checked once; the row lists every original line ininputs;EXAMPLE.com,example.com,https://example.com/and a#fragmentcount 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) andSUMMARY.unansweredNotChargedcounts 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
invalidrow 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
costPlaninSUMMARY. - When the maximum is reached, the run stops: the status message says so,
SUMMARY.statusisLIMIT_REACHEDandSUMMARY.notProcessedhas 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 showsrateLimited. A URL that still answers 429 is reported asrestrictedwith anote. The check is charged once, retry included.
Price comparison (checked September 2026)
| Actor | Price per 1,000 URLs (free plan) | Redirect map (expected target) check |
|---|---|---|
| Bulk URL Status & Redirect Checker (this Actor) | $1, no start fee | Yes |
| automation-lab/bulk-url-status-checker | $0.08 + $0.005 start fee | No |
| logiover/bulk-url-status-checker | $2.50 | No |
| automation-lab/http-status-checker | $0.575 + $0.035 start fee | No |
| 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
- Paste your URLs, one per line (bare domains like
example.comare fine), or use Paste URLs as text, URL list files or Sitemap URL. - For a migration, fill in Redirect map with one
old URL, expected new URLpair per line. Paths only (/old,/new, as exported by Shopify or WordPress)? Also fill in Redirect base URL. - 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.
- Click Start and export the results as CSV, Excel or JSON.
How to check a list of URLs for 404s and redirects in bulk
- 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.
- Turn on Only report problems so the output lists only 404s, server errors, redirects and dead hosts.
- Start the run. Sort by
statusClass:broken(4xx),server-error(5xx),error(DNS, SSL, timeout, loop; the reason is inerror). Redirected URLs haveredirectsabove 0, every hop inredirectChainand the same chain as one text cell inredirectPath(handy in CSV and Google Sheets). Each row keeps your original line ininput, so you can match it back to the sheet.
How to check 301 redirects after a site migration
- 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 URLpairs. Quoted CSV cells are fine. For pages you deleted on purpose, writeold URL, 410(or404/gone). - 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. - Start the run.
SUMMARY.redirectMapcounts passed and wrong redirects; filter the rows onmatchesExpected: falseorissueto get the fix list (wrong-target,not-redirected,302-not-301,chain,https-downgrade,target-broken,canonical-mismatch; for removed pagesstill-live,redirected,wrong-status).
How to find broken URLs and redirect chains in a sitemap
- Put the sitemap address (e.g.
https://example.com/sitemap.xml) into Sitemap URL; sitemap indexes and.gzfiles are followed. - 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
noindexor another canonical) is a sitemap entry to fix. - 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" }]
| issue | Meaning |
|---|---|
wrong-target | Redirects, but not to the expected URL |
not-redirected | The old URL does not redirect at all |
target-broken | Redirects to a page that returns an error |
chain | Reaches the right page through more than one redirect |
302-not-301 | The first redirect is temporary (302/303/307); use 301 or 308 for moved pages |
https-downgrade | A redirect in the chain goes from https to http |
canonical-mismatch | Only 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-status | Removed 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
| statusClass | Meaning |
|---|---|
ok | 2xx after following redirects |
broken | 4xx such as 404 Not Found or 410 Gone |
server-error | 5xx |
restricted | 401, 403, 429, or a site known to block automated checks (LinkedIn, X/Twitter, Facebook…). Usually works in a normal browser |
error | No HTTP answer: DNS, SSL, refused connection, timeout, redirect loop |
invalid | Not 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 readrobots.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 asinvalid(not charged). - Requests come from Apify's servers. Some sites block data-center traffic or show a bot challenge; those URLs are reported as
restrictedorerroreven 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.