Find broken links
Links rot quietly — a vendor renames a page, a partner shuts down a blog, a PDF gets moved to a new folder, and six months later a chunk of your site is pointing at dead ends nobody noticed. This endpoint crawls the links on a page and tells you exactly which ones no longer resolve, so you find out from a report instead of from a visitor.
Run it online
Run this on our servers with your account. Free tools run in your browser; this one bills your KIT balance per the price above.
The slow decay every site has
Nobody breaks their links on purpose. They break because the web underneath a page keeps changing after the page was published: a supplier restructures their site, an old campaign landing page gets retired, an internal link points at a post that was later deleted. Each broken link is small on its own, but a site that accumulates enough of them starts to look neglected to both visitors and to search engines crawling it. web.broken_links exists to surface that decay on a schedule you control, rather than letting a support ticket or a lost sale be the first signal.
What the crawl actually checks
POST /web/broken-links with a URL, get a task_id back immediately, and receive a structured report by signed webhook or a signed link once the async job finishes. The report lists each link found on the page along with its resulting status: a clean 200, a 404, a server error, a timeout, or a redirect chain worth knowing about, so you're not just told 'something is broken' but exactly which link and why.
Internal links versus outbound references
A checker like this earns its keep on both fronts: internal links are entirely within your control and every 404 among them is a fixable mistake, while outbound links to other domains decay for reasons you'll never control and simply need periodic re-checking. Treating both the same way and reviewing the report on a routine basis is how sites that publish or reference a lot of content stay ahead of the drift, instead of discovering it during a redesign two years later.
Where it fits in a maintenance routine
Teams that manage content-heavy sites, documentation portals or resource directories run this after every major content migration and again on a recurring schedule, because link rot never announces itself — it's only visible once you go looking. Priced per request, checking a batch of pages after a site migration or before a quarterly content audit costs the same whether the pages turn out clean or riddled with dead links, which makes it easy to run routinely rather than only when something is already suspected to be wrong.
What you can do with it
Post-migration verification
A team moving a site to new URLs runs the checker across key landing pages right after launch to catch any internal links that weren't updated.
Documentation portal upkeep
A docs team scans reference pages on a recurring schedule so links to external tools and APIs that got renamed or retired are caught before a developer hits them.
Pre-publish editorial check
An editor runs a long-form article through the checker before it goes live to confirm every cited source link still resolves.
Resource directory maintenance
A curated links directory re-checks its full listing periodically since outbound sites change or disappear without any notice.
FAQ
How do I check a page for broken links with the API?
POST the page URL to /web/broken-links, keep the returned task_id, and collect the report of dead and redirected links by webhook or a signed link valid for 24 hours.
Is the broken link checker API free?
No, there is no free tier or trial; it costs $0.002 per request, and a failed task is never charged.
Does it check both internal and external links?
Yes, it reports the status of every link found on the page regardless of whether it points within the same domain or somewhere else.
What counts as a broken link in the report?
404s, server errors and timeouts are flagged as broken, and redirect chains are reported separately since they resolve but may still be worth cleaning up.
Can I scan an entire site at once?
Each request checks the links found on the URL you submit, so scanning a full site means queuing a request per page you want covered.
How fast does a check run?
It runs asynchronously — you get a task_id right away and the report arrives by webhook or signed link once the crawl finishes, which depends on how many links the page contains.
Is this endpoint live yet?
Yes, it's live and accepting requests now.
Is my crawl data kept afterward?
No, page content and results are deleted after the retention window and are never used for training.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, and soon from our app, email and Telegram.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/web/broken-links \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"input":"…"}'const res = await fetch("https://api.kit.forhosting.com/web/broken-links", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"input": "…"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/web/broken-links",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"input": "…"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/web/broken-links", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"input":"…"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"input":"…"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/web/broken-links", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"input": "…"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "web.broken_links",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
timeout_sec | 30 |
max_crawl_pages | 25 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
413 | input_too_large | The file exceeds the size limit. |