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
5
Total users
4
Monthly active users
9 hours ago
Last modified
Categories
Share
What does Bulk URL Status Checker do?
Bulk URL Status Checker checks up to 100,000 URLs and returns one row per URL with the HTTP status code, every redirect hop, the final URL, and the reason when a link fails (404, server error, DNS error, expired SSL certificate, timeout).
Paste a URL list → one row per URL, exportable as CSV. On the Free plan, $0.001 per URL that gets an answer, with no start fee. In this real result from the example below, http://python.org returns a 301 redirect to https://www.python.org/; the final response is 200:
{"url": "http://python.org","finalUrl": "https://www.python.org/","status": 200,"redirects": 1,"redirectChain": [{ "url": "http://python.org", "status": 301, "location": "https://www.python.org/" }]}
Large lists (600+ URLs): put one address per line in URLs, or paste/upload a CSV with a URL column in any position (UTF-8 BOM, Windows line endings, and comma, semicolon or Tab-separated Excel exports work). An uploaded list without a header also accepts multiple full URLs on one row. Full URLs and bare domains may be mixed. Defaults are 40 parallel checks overall and 8 per host; keep these for mixed sites, and raise Parallel requests per site only for a site you control. A silent URL gets one free slower re-check, so many timeouts on the same host can still take minutes. SUMMARY.elapsedSeconds and SUMMARY.slowReasons show timeout count, hosts with many URLs, 429-throttled hosts and effective limits. Only report problems hides clean rows but still checks and charges each answered URL once.
- Full redirect chains: every hop with its status and
Locationheader, plus redirect-loop detection. - Redirect-map QA: paste
old URL, expected new URLpairs and getmatchesExpectedwith a specific issue. - Polite parallelism: up to 40 URLs at a time across sites, but at most 8 on one site; timeout re-checks use at most 2 per site.
- Read 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. - SSL certificate expiry: for https URLs,
sslDaysLeft,sslValidTo,sslIssuerandsslValid, withsslExpiringwhen fewer than 14 days are left (configurable). One check per host, no extra charge.
Minimal input:
{"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: one row per URL with status, statusClass, redirectPath, finalUrl, error reason, and more.
Per-URL assertions for release checklists
API request objects in urls, startUrls or urlSources can put assertions in userData:
{"startUrls": [{ "url": "https://example.com/", "userData": { "expectedStatus": [200, 304], "bodyContains": ["Example Domain"], "bodyNotContains": ["Maintenance", "Error"], "expectedFinalUrl": "https://example.com/" } },{ "url": "https://www.python.org/", "userData": { "expectedStatus": 200, "maxResponseMs": 2000 } }]}
expectedStatus accepts one HTTP code or a list of allowed codes (100–599). Every bodyContains term must appear; no bodyNotContains term may appear. Each accepts one string or up to ten strings, ignoring case and checking the first 256 KB of the final HTML, JSON or text response. expectedFinalUrl compares the final URL using the same rules as migration targets (host case and a trailing slash are ignored). A body is downloaded only for URLs with body assertions or the existing HTML-signals option; when both apply, the same download is used.
Rows with assertions add assertionPassed (true, false, or null when a required body or response time could not be read) and, on failure, assertionIssues (status, response_time, body_missing, body_forbidden, final_url). A separately failed condition remains a failure even if the body is unreadable. SUMMARY.assertions counts checked, passed, failed and unknown rows. Failed assertions appear with Only report problems. Billing stays one url-checked event per checked URL; assertions add no event.
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 into 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 (migration check) 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 (for relative paths). - 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. - Paste it into Redirect map (migration check), header row included, or put the link of the CSV or Google Sheet into Redirect map file (CSV or Google Sheet link) (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 (for relative paths) 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
Up to 100,000 URLs per run in total.
{"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 (migration check) (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 (for relative paths) ("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, invalidReasons, elapsedSeconds, slowReasons (first-pass timeouts, 429-throttled hosts, hosts with more URLs than the per-host limit, effective limits), 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.
- 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.
FAQ
Q1: How much does it cost?
$1 per 1,000 URLs, no start fee. Higher Apify plans get volume discounts (see the Pricing tab). Invalid lines, duplicate URLs, and URLs that never answer are free.
Q2: What inputs can I use?
A plain URL list in URLs or Paste URLs as text, a Google Sheets link, a .txt/.csv file, a Google Drive or Dropbox file link, or an XML sitemap in Sitemap URL. For migrations, use Redirect map (migration check) (or Redirect map file (CSV or Google Sheet link)) with Redirect base URL (for relative paths) for path-only exports.
How should I format a large URL list, and why was my run slow? Use one address per line or a CSV URL column; mixed full URLs and bare domains are supported. Leave parallel requests at 40 overall / 8 per host for most lists. SUMMARY.invalidReasons explains free invalid rows; SUMMARY.slowReasons identifies timeouts, 429 slowdowns and large single-host groups. A run maximum charge can stop early; check SUMMARY.notProcessed.
Q3: What is the redirect map check?
You provide two columns: old URL, expected new URL. Each line comes back with matchesExpected: true/false and a specific issue like wrong-target, chain, 302-not-301, https-downgrade, not-redirected, or target-broken. For removed pages, write old URL, 410 (or 404 / gone) and they pass when the URL answers 404 or 410.
Q4: Which results are free?
Invalid lines (for example not a url, Acme Inc, or localhost), duplicate URLs (checked once; the row lists every original spelling in inputs), unreadable list files or sitemaps, relative redirect-map paths without a base URL, empty / over-limit inputs, and URLs that never answer even after the free slower re-check are not charged (charged: false).
Q5: Does it check SSL certificates?
Yes. For https URLs, it returns sslDaysLeft, sslValidTo, sslIssuer, and sslValid, with sslExpiring when fewer than the configurable warning days remain (default 14). One check per host, no extra charge.
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.