Bulk URL Status & Broken Link Checker icon

SEO & ads intelligence · Developer tools

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 in one dataset, without ever downloading page bodies.

Free Apify credit covers a first run. No credit card to try.

What this Actor 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-cffi Chrome 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.

Input

Paste this into the Apify Console, or send it as the run input over the API. Proxy settings are on by default; you rarely need to touch them.

FieldTypeRequiredWhat it does
urls array yes The URLs to check, one per line. Must be http:// or https://. Deduplicated automatically.
followRedirects boolean no When true, chase the redirect chain (up to 20 hops) and report the final destination. When false, report only the first hop and its Location header.
timeoutSecs integer no Timeout applied to every request (HEAD, GET fallback, and each redirect hop).
maxConcurrency integer no Maximum number of URLs checked in flight at once.
maxUrls integer no Hard cap on accepted input URLs after deduplication. The run fails input validation (no charge) if exceeded.
{
  "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

One row per result, schema-validated before it is written. Export JSON, CSV, Excel or XML from the run, or read it over the API.

input_urlfinal_urlfinal_status_coderedirect_chainredirect_countresponse_time_mscontent_typecontent_lengthserveris_brokenerror_classsecurity_headers

{
  "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

EventPriceWhen
Actor start$0.20Once per run, covers warm-up and proxy session setup.
URL checked$0.0020Per result written to the dataset.

You pay only for results that land. Cap any run with maxTotalChargeUsd. See pricing & billing for worked examples.

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.
  • maxUrls is 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.

Ready to run it?

Open the listing on Apify, paste the input above, and watch rows land. If it ever breaks, it is our problem before it is yours.

Related Actors

Teams that run this also run