Website Broken Link Checker avatar

Website Broken Link Checker

Pricing

from $0.43 / 1,000 link checkeds

Go to Apify Store
Website Broken Link Checker

Website Broken Link Checker

Finds the broken links on the pages you name. Paste URLs or bare domains and press Start: every link found is followed to its final HTTP status, one row per link, with the verdict, anchor text, source page, redirect count and timing. No login, no browser.

Pricing

from $0.43 / 1,000 link checkeds

Rating

0.0

(0)

Developer

Pradio Actors

Pradio Actors

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Share

Website Broken Link Checker reads the pages you name and returns one row per link found on them: the checked URL, its final HTTP status, an ok, broken or error verdict, the anchor text and the page it appeared on. Paste URLs or bare domains, press Start, and each page is fetched once, its links collected and every one checked end to end. Sixteen fields land on each data row. It checks the links on the pages you list; it is not a site crawler and it does not fix anything.

Each checked link costs $0.0008, billed only after its row is in your dataset. In a measurement on 40 sites it had never seen, 35 answered with checked links, a hit rate of 87.5% (3 returned no checkable links, 2 errored). A run that finds no links returns one uncharged row that says so.

BuyerWhat they run it for
Website ownersFinding dead links on their own pages before visitors or crawlers do.
SEO practitionersFinding dead links that leak ranking; each row names the page and the anchor to fix.
Teams after a migration or redesignChecking that old URLs redirect somewhere instead of dying.
Content maintainersAuditing outbound links that rot quietly over time.

Features

  • Checks every link on the pages you name. Paste full URLs or bare domains; each page is fetched once and every anchor on it is checked.
  • HEAD first, GET when refused. A cheap HEAD request leads; a server that answers 405 or 501 is asked again with GET.
  • Redirects followed and counted. Up to 10 redirects are followed per link, so final_url and redirect_count are read, not assumed.
  • Three honest verdicts. ok below HTTP 400, broken at 400 or above, error when a link never answers within 15 seconds.
  • Checked once across pages. A footer link repeated across start pages is checked once; all_sources lists every place it appeared.
  • Unusable entries answered, not dropped. An entry in queries that is not a fetchable URL gets its own free row with status bad_url and the reason.
  • Plain HTTP, no browser, no login. Up to 8 links are checked at once, with a 15 second limit per request.
  • Caps you control. maxResultsPerQuery limits the links checked per page; maxItems limits the whole run, 100 by default.

What you can count on

  • You pay only for rows the run judged; a row it could not judge is pushed as an uncharged ITEM_STATUS row with the reason on it.
  • Every row is charged only after it is written to your dataset; a row you cannot see is never billed.
  • A run that finds nothing returns one NO_RESULTS row that says so, never an empty dataset, and it is not charged.
  • A spending limit stops the run cleanly: one STOPPED_EARLY row reports how many rows were returned and how many were not.
  • Every run writes a RUN_SUMMARY entry with rowsFetched, rowsPushed, rowsCharged and duplicatesDropped, so a short run and a broken one are told apart.
  • If a start page cannot be read or changes what it answers, the run fails with the error in the log; it never returns rows full of nulls and calls it success.
  • No value is invented: a field the checked page does not show is null, and the output table says which.

Why this one

  • This Actor bills $0.0008 per checked link; the most-used alternative on this platform bills $0.001, measured on 2026-09-16.
  • Run on the same pages that day, the alternative left its redirect-chain field empty on 12 of its 19 rows and its error field on 16 of 19. Here the redirect count is on every row, and a link that never answers carries the reason in status_text.
  • The hit rate above is measured on sites this Actor had never seen, not on a best case.
  • Every row names the page the link was found on, so a dead link is a fix, not a riddle.

One real row from a run over the example pages in the default input:

{
"url": "https://crawler-test.com/links/not_found/foo1",
"final_url": "https://crawler-test.com/links/not_found/foo1",
"status": 404,
"status_text": "Not Found",
"classification": "broken",
"is_broken": true,
"source_domain": "crawler-test.com",
"source_url": "https://crawler-test.com/links/broken_links_internal",
"anchor_text": "Broken Internal Link 1",
"element": "a",
"all_sources": [
{
"source_url": "https://crawler-test.com/links/broken_links_internal",
"anchor_text": "Broken Internal Link 1",
"element": "a"
}
],
"method": "HEAD",
"redirect_count": 0,
"duration_ms": 291,
"checked_at": "2026-09-18T06:29:01.047Z",
"row_type": "ROW"
}

Each row is one link checked end to end. Every field below sits on every data row; a value a check cannot produce is null, never absent.

FieldWhat it is
urlThe link as it was found, made absolute. This is the row's key: one row per unique link URL.
final_urlWhere the link ended after its redirects; the same as url when it never redirected, null when it never answered.
statusTwo shapes by design, documented in the table below: an integer HTTP status on a checked-link row, or the miss word bad_url on an ITEM_STATUS row.
status_textThe server's status line, like Not Found, or the failure reason when nothing answered.
classificationThe verdict on the row: ok, broken or error.
is_brokentrue on a confirmed 4xx or 5xx, false on a healthy answer, null when unproven.
source_urlThe start page this link was first found on.
anchor_textThe clickable text of the link on that page.
elementThe HTML element the link came from; a here, since only anchor tags are checked.
all_sourcesEvery page and anchor text the same link appeared on, when more than one page carries it.
methodHEAD or GET: how the check was made.
redirect_countHow many redirects were followed before the final answer; a URL answered directly shows none.
duration_msHow long the check took in milliseconds, redirects included.
checked_atThe ISO timestamp of the check.
source_domainThe host of the start page the link was found on (one of the pages you named); the link's own host is in url.
row_typeROW on every checked link; the status kinds below mark the run's own messages.

The Overview tab shows the per-link fields; the All fields tab adds duration_ms, checked_at and source_domain. Across the captured runs (12 rows locally, 1,000 on the platform, 2026-09-18), no field was empty on every row.

status carries two shapes by design:

status valueOn which rowsWhat it means
An integer HTTP code, like 200 or 404ROWThe status the link's final address answered with; null when no response came back.
bad_urlITEM_STATUSThe queries entry was not a usable URL. The entry is named in url, the reason sits in reason, and the row is pushed free: a malformed entry is answered, never dropped.

row_type says what a row is. ROW is a checked link. ITEM_STATUS is a per-item miss the schema declares free: pushed so you see it, never billed. NO_RESULTS means no page produced a checkable link. STOPPED_EARLY means your spending limit ended the run. Status rows also carry the run's bookkeeping:

FieldWhat it is
reasonWhy the run ended or the item missed; on status rows only.
rowsFetchedLinks read before the run ended; on status rows only.
rowsReturnedRows returned before the spending limit stopped the run; on STOPPED_EARLY.
rowsRemainingRows not returned when the spending limit stopped the run; on STOPPED_EARLY.

Pricing

You pay $0.0008 per checked-link row, the result-returned event, charged only after the row is written. Apify also bills its own apify-actor-start event once per run, $0.00005 at this Actor's memory size. Status rows, bad_url miss rows and dropped duplicate links are free. Every checked link is billed, including broken and error verdicts: the verdict is the answer you paid for.

Rows returnedLink chargesStart eventTotal
100$0.08$0.00005about $0.08
1,000$0.80$0.00005about $0.80
10,000$8.00$0.00005about $8.00

A start page carries more than one link. The 35 pages that answered in the measurement produced 3,504 link rows in total, about 100 per page, so budget by sweep:

  • 100 start pages: about 88 answer, about 8,800 links checked, about $7.04 plus the start event.
  • 1,000 start pages: about 875 answer, about 87,600 links checked, about $70.08 plus the start event.

Your pages will differ, and maxItems caps a run at 100 rows unless you raise it.

Duplicates are billed once, not once per sighting. If 50 link sightings across your pages deduplicate to 47 unique URLs, you pay for 47 checks; the repeats join all_sources free.

  1. Open the Actor and press Try for free.
  2. Paste your start pages into Queries, one URL or bare domain per line. The input carries three example pages from a public test site; replace them with yours.
  3. Optionally set Max Results Per Query and Maximum items.
  4. Press Start. Rows land in the dataset as links are checked, and the run ends when every page is read.

Example input:

{
"queries": ["https://crawler-test.com/links/broken_links_internal", "https://crawler-test.com/links/broken_links_external", "https://example.com/"],
"maxItems": 100
}

Or start a run over the API:

curl -X POST "https://api.apify.com/v2/acts/Pradio~broken-link/runs?token=YOUR_APIFY_TOKEN" -H "Content-Type: application/json" -d "{\"queries\":[\"https://example.com/\"]}"

Input

InputDefaultWhat it does
queriesthree example pagesThe start pages to check, one URL or bare domain per line. An entry that is not a fetchable URL gets its own free bad_url row with the reason, so a typo is answered, never dropped.
maxResultsPerQuerynoneThe most links checked from one start page. Unset means every link found is checked.
maxItems100The most link rows one run returns in total. Rows past the cap are dropped, and the run summary shows how many links were checked.

queries

Each entry is one start page: a full URL like https://example.com/blog, or a bare domain like example.com, which is fetched over HTTPS. The page is fetched once, its anchors are collected and each unique link is checked. Only the pages you list are read; the Actor checks the links on them and does not crawl further. An entry nothing can be fetched for, a typo or an unrecognisable line, is answered on its own uncharged row: status bad_url, the entry in url, the reason in reason.

Output

Rows land in the default dataset as links are checked. Four extras tell you how a run went:

  • ITEM_STATUS: a queries entry nothing could be fetched for, named in url with status bad_url and the reason in reason. Pushed so you see it, never billed.
  • NO_RESULTS: no start page produced a checkable link. One uncharged row carries reason and rowsFetched, so an empty answer is an answer, not silence.
  • STOPPED_EARLY: your spending limit ended the run. One uncharged row carries rowsReturned and rowsRemaining; raise the limit and re-run for the rest.
  • RUN_SUMMARY: an entry in the run's key-value store carrying the counts: links fetched, rows pushed, rows charged, duplicates dropped, stopped early or not.

A run where every start page fails pushes nothing and fails with the reason in the log. No rows are billed, because none were written.

What can you do with the data?

Sweep your own site before a launch. Run the checker over the pages, filter classification to broken, and hand the list to whoever fixes it. Each row already carries the dead URL, the page it sits on and the anchor text to search for.

Audit a migration. Old URLs that still redirect show how many redirects they took and where they landed in redirect_count and final_url. The ones that answer 404 instead are the ones to remap.

Watch outbound links on a schedule. Reference, partner and affiliate links rot quietly. Put the run on an Apify schedule, export the broken rows to a sheet, and the dataset becomes a monthly fix list.

Qualify a site you are evaluating. A page full of dead links says something about how it is maintained. One run counts them without a manual click-through.

Paste this line to give an agent this Actor through Apify's MCP server:

claude mcp add --transport http apify "https://mcp.apify.com?tools=Pradio/broken-link"

Personal data

  • A row carries only the declared link-health fields: the checked URL, its HTTP status and verdict, the anchor text and the page it appeared on. No field names or identifies a person.
  • A row keeps facts and the link's own anchor text, never the body or a substantial part of a page.
  • Every row links back to the page it was found on, so what was collected is easy to check.
  • A page that refuses the read is reported as an error row, and if no start page answers the run fails; nothing retries past a block or works around a refusal.
  • Personal data is not what a row is about, but it can appear incidentally inside anchor text or a URL path (a name in a link label, a profile slug in a path). Run it against sites you operate or are permitted to check.
  • You choose the pages, so you are the controller for the list you supply. To exclude a URL, remove it from the input; the Actor reads only what you list.

Release notes

  • 0.1.13 (2026-09-18): an unusable queries entry now gets its own free bad_url row instead of being dropped; the redirect field is redirect_count.
  • 0.1.12 (2026-09-17): the Store icon redrawn.
  • 0.1.11 (2026-09-17): the sample row on this page re-synced to a fresh capture.
  • 0.1.10 (2026-09-17): a link first seen without anchor text picks it up when a later page shows it.
  • 0.1.9 (2026-09-17): source_url and anchor_text now always describe the same sighting.
  • 0.1.8 (2026-09-17): RUN_SUMMARY is now an entry in the run's key-value store.
  • 0.1.7 (2026-09-17): all_sources entries carry the element name.
  • 0.1.6 (2026-09-17): all_sources entries use snake_case keys.

Limits

  • It checks the links on the pages you name and reports them. It is not a crawler: nothing past the listed pages is read. And it does not fix links: repair stays with you.
  • Only anchor tags are checked. javascript:, mailto:, tel:, data:, sms: and ftp: links are skipped, and so are images, scripts and stylesheets.
  • Links a page builds with JavaScript after load are not seen: pages are fetched over plain HTTP, with no browser.
  • Requests are logged-out and public, with a fixed declared user agent. There is no proxy rotation and no retry past a block: a site that refuses is an error row, not a workaround.
  • A link that never answers within 15 seconds is an error row, suspected but not proven broken.
  • A redirect chain is followed for at most 10 redirects; a longer chain returns the last redirect's status.
  • Every checked link is a billed row, including error verdicts. A run of nothing but unanswered links still bills its rows, because the check itself is the answer.

Troubleshooting

The run returned fewer rows than maxItems. maxItems is a ceiling, not a target. Fewer rows means the pages ran out of links first; rowsFetched in RUN_SUMMARY shows how many links were checked.

The dataset has one row saying NO_RESULTS. No start page produced a checkable link. That is the answer to this run, not a bug, and the row is free.

A row says bad_url in status. One queries entry was not a URL the Actor could fetch, a typo or an unrecognisable line. The row names the entry in url, gives the reason in reason, and is free. The other entries ran normally.

A row says error with is_broken empty. The link never answered within 15 seconds. It is suspected, not proven dead; re-run later to retry it.

The run failed with "no start page could be read". Every entry failed at the network level: nothing was pushed and no rows were billed. Check the URLs you pasted.

FAQ

Can I use integrations with Website Broken Link Checker? Yes. The dataset plugs into Apify integrations like Zapier, Make and webhooks, and Apify schedules can re-run the sweep weekly or monthly without you touching it.

Can I use Website Broken Link Checker with the Apify API? Yes. Start runs and read the dataset back over the API; the curl example above is the whole call.

Can I use Website Broken Link Checker through an MCP server? Yes. The line under "Use Website Broken Link Checker with AI agents" adds it to an agent through Apify's MCP server.

Is it legal to check links this way? The Actor makes the same plain HTTP requests any monitoring tool makes: it bypasses no login and keeps only link facts. You choose the pages, so point it at sites you are responsible for or allowed to test.

Something look wrong? Report it on this Actor's Issues tab in the Apify Console and it will be looked at.

Not affiliated

Website Broken Link Checker is an independent tool, not affiliated with or endorsed by any site you point it at. The example pages in the default input belong to crawler-test.com, a public test site.