Bulk URL Status & Broken Link Checker
Pricing
Pay per event
Bulk URL Status & Broken Link Checker
Check hundreds of URLs at once for status code, full redirect chain, response time, and security headers — spot broken links and redirect loops without downloading page bodies.
Pricing
Pay per event
Rating
0.0
(0)
Developer
DevilScrapes
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
🎯 What this scrapes
Point this at any list of URLs — a sitemap export, a link audit spreadsheet, an affiliate feed, a migration checklist — and get back exactly what a real visitor's browser would see for each one: the final status code, the complete redirect chain (every 301/302/307/308 hop in order), response time, and the security headers that show up in an SEO or infra audit. There's no target API to key against and no site-specific logic — any http:// or https:// URL is a valid input. Nothing gets crawled beyond the URLs you supply, and the page body is never downloaded — this is a status/health check, not a scraper.
🔥 What we handle for you
- 🌐 Every check runs through Apify Proxy — a clean vantage point for the request, not your own office or CI-runner IP.
- 🕵️ Real-browser TLS fingerprint — requests go out via
curl-cffiChrome impersonation, so a HEAD or GET from us doesn't look like a bare Python script to the target. - 🧱 Per-URL fault isolation — a DNS failure, timeout, or SSL error on one URL never takes the batch down; every URL gets its own row, always.
- 🧊 Clean, typed dataset rows — Pydantic-validated, one consistent shape whether the URL succeeded or failed, ready for JSON/CSV/Excel export.
- ⏱️ Concurrency and timeout you control — bounded in-flight requests and a per-request timeout mean one slow host can't stall the whole run.
- 💰 Pay only for delivered rows — one flat start fee, then a small per-URL charge whether that URL turned out healthy or broken (a recorded failure is still a delivered result).
💡 Use cases
- SEO audits — find broken internal and external links across a site before Google does.
- Site-migration QA — confirm every old URL 301s to the right new one, not a 404 or a redirect loop.
- Affiliate-link health monitoring — catch dead or redirected affiliate URLs before they cost you commission.
- Uptime and redirect-chain spot checks — verify a domain move or CDN change resolved the way you expect.
- Sitemap link validation — run a sitemap's URL list through in bulk and get one row per entry.
⚙️ How to use it
- Click Try for free at the top of the page.
- Fill in the input form — most fields have sensible defaults.
- Click Start. Output streams into the run's dataset.
- Export from Storage → Dataset as JSON, CSV, or Excel — or fetch via the API.
📥 Input
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
urls | array | yes | ['https://httpbin.org/status/200', 'https://httpbin.org/redirect/2', 'https://httpbin.org/status/404'] | The URLs to check, one per line. Must be http:// or https://. Deduplicated automatically. |
followRedirects | boolean | no | True | When true, chase the redirect chain (up to 20 hops) and report the final destination. When false, report only the first… |
timeoutSecs | integer | no | 15 | Timeout applied to every request (HEAD, GET fallback, and each redirect hop). |
maxConcurrency | integer | no | 10 | Maximum number of URLs checked in flight at once. |
maxUrls | integer | no | 750 | Hard cap on accepted input URLs after deduplication. The run fails input validation (no charge) if exceeded. |
proxyConfiguration | object | no | {'useApifyProxy': True} | Apify Proxy configuration. No forced proxy group — the target set is arbitrary customer-supplied hosts, not a known… |
Example input
{"urls": ["https://httpbin.org/status/200","https://httpbin.org/redirect/2","https://httpbin.org/status/404","https://this-domain-does-not-exist-devilscrapes-qa.invalid"],"followRedirects": true,"timeoutSecs": 15,"maxConcurrency": 10,"maxUrls": 750,"proxyConfiguration": {"useApifyProxy": true}}
📤 Output
Every row is one dataset item.
| Field | Type | Notes |
|---|---|---|
input_url | string | The URL as supplied in the input, echoed back. |
final_url | string | The last resolved URL — equal to input_url when no redirect occurred or followRedirects is false. |
final_status_code | integer | The last response's HTTP status code. Null on a connection failure. |
redirect_chain | array | Every 3xx hop encountered before the final response, in order, as {url, status_code}. Empty list if none. |
redirect_count | integer | Number of entries in redirect_chain. |
response_time_ms | integer | Wall-clock milliseconds from the first request to the final response, including any redirect hops chased. |
content_type | string | Content-Type response header. Null if the response omits it. |
content_length | integer | Content-Length response header, in bytes. Null if the response omits it. |
server | string | Server response header. Null if the response omits it. |
is_broken | boolean | True if final_status_code is 400 or higher, or a connection/DNS/TLS/timeout error occurred. |
error_class | string | One of timeout, dns_error, connection_error, ssl_error, too_many_redirects. Null when an HTTP response was received. |
security_headers | object | strict_transport_security, x_frame_options, content_security_policy — each nullable, passed through verbatim from the… |
Example output
{"input_url": "http://example.com","final_url": "https://example.com/","final_status_code": 200,"redirect_chain": [{"url": "http://example.com","status_code": 301},{"url": "https://example.com","status_code": 301}],"redirect_count": 2,"response_time_ms": 342,"content_type": "text/html; charset=UTF-8","content_length": 1256,"server": "ECS (nyb/1D2A)","is_broken": false,"error_class": null,"security_headers": {"strict_transport_security": "max-age=31536000","x_frame_options": null,"content_security_policy": null}}
💰 Pricing
Pay-Per-Event — you pay only when these events fire:
| Event | USD | What it is |
|---|---|---|
actor-start | $0.2 | One-off warm-up charge per run |
url-checked | $0.002 | Per URL checked, broken or not — a recorded failure is still a delivered result |
Example: 1 000 URLs checked at the rates above ≈ $2.20. No subscription, no minimum, no card to start — Apify gives every new account $5 of free credit.
🚧 Limitations
- No response-body parsing — we report status, redirects, timing, and headers, never the page content itself. Point a different tool at the ones that come back healthy if you need HTML extraction.
- No auth, session, or cookie-jar support — this checks the public, unauthenticated view of a URL. Gated or login-only pages will report whatever an anonymous request sees.
- No historical diffing across runs — every run is a stateless snapshot. Schedule the Actor and compare exports yourself if you want a change-over-time view.
maxUrlsis a hard cap (default 750, max 10 000) — an oversized list fails input validation for free rather than getting silently truncated. Split large lists across runs.- One attempt per URL, by design — we report what a real visitor sees right now, not a status we retried our way past. A transient 429, 503, or timeout is the deliverable here, not noise to smooth over, so it shows up in your dataset instead of getting hidden behind a retry loop.
❓ FAQ
Does this download the page content?
No. Every URL is HEAD-first, and on the rare server that mishandles HEAD (405/501) we fall back to a GET — but even then we read only the headers and abort the transfer. We never buffer or store a response body.
Will a broken link fail my whole run?
No. Every URL is checked in isolation. A DNS failure, timeout, or 500 on one URL never stops the others — the run always finishes SUCCEEDED with exactly one row per URL, broken or not.
Do you retry rate-limited or 5xx responses?
No, by design. See Limitations below — a single-attempt result is the point of this Actor, not a gap in it.
What counts as "broken"?
Any final status code of 400 or higher, or a connection, DNS, TLS, or timeout error that meant no status code came back at all. Either way you get is_broken: true and, for transport failures, a specific error_class.
💬 Your feedback
Spotted a bug, hit a weird edge case, or need a new field? Open an issue on the Actor's Issues tab on Apify Console — we ship fixes weekly and we read every report.