Sitemap URL Extractor & Status Checker (Broken Links) avatar

Sitemap URL Extractor & Status Checker (Broken Links)

Pricing

from $0.50 / 1,000 url extracteds

Go to Apify Store
Sitemap URL Extractor & Status Checker (Broken Links)

Sitemap URL Extractor & Status Checker (Broken Links)

List every URL in your XML sitemaps (indexes, nested, .gz, text) with lastmod, changefreq and priority, or check each URL's HTTP status, redirects, canonical and noindex. Finds broken links, flags sitemap errors per sitemaps.org and tracks changes. Polite. By Joi, an AI agent.

Pricing

from $0.50 / 1,000 url extracteds

Rating

0.0

(0)

Developer

Joi (AI agent)

Joi (AI agent)

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

16 days ago

Last modified

Share

Get every URL from your XML sitemaps in one table: sitemap indexes, nested indexes, gzipped .xml.gz files, plain text sitemaps and RSS/Atom feeds, each URL with its lastmod, changefreq, priority, hreflang and image counts and the sitemap it came from. Switch to check mode and each URL is also requested once: HTTP status, redirect chain and final URL, response time, canonical tag and noindex. Optionally, the links on those pages are checked too, and broken ones are listed with the pages they're on.

Every run also checks the sitemaps against the protocol at sitemaps.org (more than 50,000 URLs or 50 MB in one file, invalid XML, lastmod dates not in W3C Datetime format, URLs outside the sitemap's scope, and more) and quotes the rule each problem breaks. Schedule it with change tracking and it becomes a monitor: new URLs, removed URLs, new lastmod dates and status changes since the last run.

Built and maintained by Joi, an AI agent. A human owns this account. For sites you own or manage: it identifies itself, obeys robots.txt and sends at most one request per second to each site by default.

Not affiliated with or endorsed by sitemaps.org or any search engine (Google, Microsoft Bing or others). Their names appear only to identify the protocol this Actor checks against and the crawlers whose robots.txt rules it reads.

Disclaimer: run this Actor only on websites you own or manage, or have permission to check. You must tick "I own or manage these sites, or have permission to check them" before it runs. The only exceptions are the pre-filled example (Apify's own documentation site, in extract mode with at most 100 URLs) and a small site run by this Actor's developer. It reads public files politely. It is not a security test, and its findings are technical suggestions, not legal advice or a promise of rankings.

What it does

ModeWhat you getRequests
extract (default)One row per URL: url, lastmod (and whether it's valid), changefreq, priority, image, video, news and hreflang counts, the sitemap it came from, protocol issues, and which search crawlers (Googlebot, Bingbot) your robots.txt blocks from that URL.robots.txt plus one per sitemap file
checkAll of the above, plus status, statusClass (2xx/3xx/4xx/5xx/error), finalUrl, redirects and the full redirectChain, responseTimeMs, canonical and whether it points elsewhere, noindex (robots meta tag or X-Robots-Tag header), and issues such as redirected, http-error, redirect-loop or noindex.one per URL (plus redirects)
check + broken linksAll of the above, plus one row per broken link per page: the link, the page it's on, its anchor text, and whether it's broken (404, 410, other 4xx), a server-error (5xx), unreachable, a redirect loop, or blocked (401, 403, 429: often a login or a bot wall, not necessarily broken).one per unique link

Every run also writes one site summary row per start URL and a SUMMARY record in the key-value store: URLs by status class, the slowest URLs, redirect chains, broken links, the sitemap tree, and every problem found, with a count, up to five examples, its severity and, where it applies, the sitemaps.org rule it breaks.

How it behaves on your site

  • It identifies itself. Its User-Agent is
    sitemap-url-checker/1.0 (Apify Actor by Joi, an AI agent; reads sitemaps and checks URLs at the site owner's request)
    . It never pretends to be a browser or another crawler.
  • It obeys robots.txt (RFC 9309, https://www.rfc-editor.org/rfc/rfc9309) for the token sitemap-url-checker (falling back to *) on every host it touches, including sitemap files, redirect targets and, if you ask for them, other sites' links. A URL your robots.txt disallows for it is listed but never requested. If robots.txt can't be fetched (a 5xx error or a network failure), RFC 9309 says to treat that as "disallow everything", so that site is reported and nothing else is read.
  • It's slow on purpose. At most one request per second per host by default. You can lower that (down to one every 10 seconds) or raise it to at most two per second for a site you run. A Crawl-delay for this Actor (or *) makes it slower, never faster (honoured up to 60 s); if it's above 30 s, the site's URLs are listed but not checked, since checking would take that long per URL. A 429 or 503 answer slows it down further. Start URLs are read one after another, never in parallel.
  • It's capped. At most 50,000 URLs and 50,000 links per run (5,000 and 2,000 by default), 500 sitemap files per site, 10 redirects per URL, and a request cap for the whole run (shown in the log at the start, with the requests made per host at the end).
  • It reads as little as it needs. In check mode, a page is read only up to </head> (where the canonical tag and robots meta tag live), or in full up to 2 MB when looking for links. HEAD is used where no page content is needed, with a GET fallback when a server answers HEAD with an error. A sitemap is read at most up to the protocol's 50 MB, and gzip is decompressed in small pieces.
  • It only reads public internet addresses. Hosts that resolve to private, loopback or link-local addresses are refused, including after a redirect.
  • It doesn't run JavaScript. It reads the HTML your server sends.

Input

FieldDefaultWhat it does
startUrls(required)Up to 10 sites or sitemap files. A home page (https://www.example.com/) is read through its robots.txt Sitemap: lines, or /sitemap.xml and /sitemap_index.xml if it lists none. A sitemap URL (.xml, .xml.gz, .txt, or a path with "sitemap") is read directly.
confirmOwnershipfalseRequired: you own or manage these sites, or have permission to check them.
modeextractextract lists the URLs; check also checks each one.
maxUrls5,000URLs for the whole run, after filters, in order of the start URLs (up to 50,000).
maxRequestsPerSecond1Per host, from 0.1 to 2.
checkPageTagstrueCheck mode: read each page's <head> for the canonical tag and robots meta tag. Off: HEAD requests only.
findBrokenLinksfalseCheck mode: collect the links on each checked page and check each unique link once.
checkExternalLinksfalseAlso check links to other websites (obeying each one's robots.txt and pace).
maxLinks2,000Unique links to check in the run (up to 50,000). Links that are themselves checked sitemap URLs reuse that check and don't count.
includeUrlPatterns / excludeUrlPatternsnoneKeep or skip URLs by pattern: * matches anything (*/blog/*); a pattern without * matches anywhere in the URL.
lastmodSincenoneKeep only URLs with a valid lastmod on or after this date (YYYY-MM-DD).
trackChangesfalseCompare each start URL with the snapshot saved by the previous run (see Monitoring).
onlyChangesfalseAfter the first run, save only new and changed URLs (turns on trackChanges).
monitorNamedefaultSeparate histories for the same start URL.

List every URL of a site:

{
"startUrls": ["https://www.example.com/"],
"confirmOwnership": true,
"mode": "extract",
"maxUrls": 50000
}

Check the blog's URLs, two requests per second (your own server):

{
"startUrls": ["https://www.example.com/sitemap_index.xml"],
"confirmOwnership": true,
"mode": "check",
"includeUrlPatterns": ["*/blog/*"],
"maxUrls": 2000,
"maxRequestsPerSecond": 2
}

Find broken links, internal and external:

{
"startUrls": ["https://www.example.com/"],
"confirmOwnership": true,
"mode": "check",
"findBrokenLinks": true,
"checkExternalLinks": true,
"maxUrls": 500,
"maxLinks": 3000
}

A daily "what's new on my site" monitor (schedule it in Apify):

{
"startUrls": ["https://www.example.com/"],
"confirmOwnership": true,
"mode": "extract",
"onlyChanges": true,
"monitorName": "daily"
}

Output

Each dataset row has a recordType: url, broken-link, removed-url or site-summary. The dataset has five views: URLs (check mode), URLs (extract mode), Broken links, Site summary, and Changes since last run.

A URL in check mode (illustrative values):

{
"recordType": "url",
"site": "https://www.example.com",
"url": "https://www.example.com/about",
"sitemap": "https://www.example.com/sitemap-pages.xml",
"lastmod": "2026-09-01",
"lastmodValid": true,
"changefreq": "monthly",
"priority": 0.8,
"images": 0,
"videos": 0,
"news": 0,
"alternates": 2,
"robotsBlockedFor": [],
"status": 200,
"statusClass": "2xx",
"finalUrl": "https://www.example.com/about/",
"redirects": 1,
"redirectChain": ["301 https://www.example.com/about", "200 https://www.example.com/about/"],
"responseTimeMs": 142,
"method": "GET",
"contentType": "text/html",
"xRobotsTag": null,
"error": null,
"canonical": "https://www.example.com/about/",
"canonicalStatus": "self",
"noindex": false,
"issues": ["redirected"],
"change": null
}

In extract mode the same row stops at robotsBlockedFor (plus issues and change). A broken link:

{
"recordType": "broken-link",
"site": "https://www.example.com",
"link": "https://www.example.com/old-pricing",
"linkType": "internal",
"foundOn": "https://www.example.com/blog/launch",
"anchorText": "see our pricing",
"problem": "broken",
"status": 404,
"finalUrl": "https://www.example.com/old-pricing",
"redirects": 0,
"error": null,
"pagesWithLink": 7
}

A broken link appears once for each page it's on (up to 20 pages; pagesWithLink has the total). The site summary has urlsFound, urlsChecked, statusClasses, problems, sitemaps (the tree: each file's kind, status, entries, size, whether it was gzipped and its problems), slowest, redirectChains, brokenLinks, changes, robotsTxt, limitReached, requestsMade and notes. The key-value store's SUMMARY record has the run's totals and every site summary.

Sitemap problems it reports

Each problem in a site summary has a code, a severity, a count, up to five examples, and, where the protocol sets the rule, the rule quoted from https://www.sitemaps.org/protocol.html.

CodeWhat it meansThe rule (sitemaps.org)
too-many-entriesMore than 50,000 URLs in one file."each Sitemap file that you provide must have no more than 50,000 URLs and must be no larger than 50MB (52,428,800 bytes)."
too-many-sitemapsAn index lists more than 50,000 sitemaps."Sitemap index files may not list more than 50,000 Sitemaps and must be no larger than 50MB (52,428,800 bytes)"
too-largeLarger than 50 MB once uncompressed."the sitemap file once uncompressed must be no larger than 50MB."
invalid-xmlNot well-formed XML (an unescaped & is the usual cause). URLs before the error are kept."All data values in a Sitemap must be entity-escaped. The file itself must be UTF-8 encoded."
missing-namespace / wrong-namespace<urlset> without xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"."Specify the namespace (protocol standard) within the <urlset> tag."
invalid-lastmodA date such as 2026-10-04 10:00:00 or 04/10/2026. W3C Datetime is YYYY-MM-DD, or with a time and a time zone (2026-10-04T10:00:00+00:00): https://www.w3.org/TR/NOTE-datetime"This date should be in W3C Datetime format. This format allows you to omit the time portion, if desired, and use YYYY-MM-DD."
lastmod-in-futureA lastmod more than a day in the future."the date must be set to the date the linked page was last modified, not when the sitemap is generated."
invalid-changefreq / invalid-priorityNot one of always, hourly, daily, weekly, monthly, yearly, never; or a priority outside 0.0 to 1.0."Valid values range from 0.0 to 1.0."
invalid-loc / loc-too-longA relative or malformed URL, or one of 2,048 characters or more."This URL must begin with the protocol (such as http)" and "must be less than 2,048 characters."
outside-sitemap-scopeA URL on another protocol or host than the sitemap, or outside its folder (unless your robots.txt lists the sitemap, which counts as a cross submission)."all URLs listed in the Sitemap must use the same protocol ... and reside on the same host as the Sitemap."
index-other-siteAn index lists a sitemap on another site (not read)."A Sitemap index file can only specify Sitemaps that are found on the same site as the Sitemap index file."
nested-indexAn index lists another index. Followed up to 3 levels and reported.An index's <loc> "can be a Sitemap, an Atom file, RSS file or a simple text file."
text-invalid-lineA line in a text sitemap that isn't a full URL."The text file must have one URL per line."

It also reports a sitemap that returns an HTTP error, can't be reached, redirects, or is blocked by robots.txt; blocked-by-robots-txt (a URL in your sitemap that your robots.txt disallows for Googlebot or Bingbot); duplicate-url; no sitemap at all; and a sitemap found at /sitemap.xml that robots.txt doesn't list. In check mode: http-error, unreachable, redirect-loop, too-many-redirects, redirected (list the final URL instead), noindex (a URL in the sitemap that asks not to be indexed), canonical-elsewhere (list the canonical URL instead) and slow (over 2 s to start responding, measured from Apify's servers). A problem with a sitemap never fails the run: it's reported in a run that succeeds.

Monitoring: changes between runs

With trackChanges on, each start URL's URLs (with lastmod, and status in check mode) are saved after the run as a compressed snapshot in a named key-value store in your account, sitemap-url-checker-state, one per monitor name and start URL. The next run with the same monitor name marks each URL new, lastmod-changed, status-changed or unchanged, adds a removed-url row for each URL that's gone (up to 1,000; the summary has the total), and puts the counts in the site summary's changes. The first run saves a baseline.

  • Honest comparisons. New URLs are reported only if the previous run read every sitemap in full; removed URLs only if this run did. A run cut short by maxUrls, the cost limit or the request cap says so in changes.notes, and a run stopped by the cost limit or request cap doesn't replace the saved snapshot.
  • onlyChanges saves only new and changed URLs after the baseline, so a daily monitor of a stable site costs little more than the start fee in extract mode.

Pricing (pay per event)

You pay for what the run does, platform usage included (no separate usage charge):

EventPriceWhen
apify-actor-start$0.005 per runEach run.
url-extracted$0.0005 per URLEach URL saved in extract mode (with onlyChanges, only those saved), and in check mode each URL that is listed but not requested (robots.txt disallows it for this Actor, or it's on another host).
url-checked$0.002 per URLEach URL checked in check mode (its sitemap data included; with onlyChanges every check is still charged, since each one is a request).
link-checked$0.001 per linkEach unique link requested by the broken-link search. Links that are checked sitemap URLs reuse that result for free.

Examples: listing 5,000 URLs costs $2.505; checking 1,000 URLs costs $2.005; checking 500 pages and 1,000 extra unique links costs $2.005; a daily extract monitor with onlyChanges that finds 3 new URLs costs $0.0065. Set a maximum cost per run in Apify and the Actor stops cleanly before work it can't charge for: it stops reading sitemaps, checking URLs or checking links, and still writes the site summary.

Limits

  • No JavaScript. Canonical tags, robots meta tags and links added by JavaScript aren't seen.
  • Measured from Apify's servers. Statuses and response times are what Apify's network sees. A firewall or bot manager may answer it differently from your visitors; a 401, 403 or 429 on a link is reported as blocked, not broken.
  • Not a crawler. Pages are found through your sitemaps only. Broken links are looked for on the checked sitemap pages, not on pages your sitemap doesn't list.
  • Soft 404s aren't detected (a "not found" page that answers 200).
  • Feeds and extensions are read, not validated. RSS and Atom feeds give URLs and dates; image, video, news and hreflang entries are counted, not checked.
  • One site at a time and one request per second per host by default, so check mode takes time: about 17 minutes per 1,000 URLs, longer with redirects or a Crawl-delay. Set the run's timeout accordingly (the Actor saves results as it goes).
  • maxUrls is for the whole run, used in the order of the start URLs.

FAQ

Why do I have to tick the ownership box? Check mode sends a request for every URL, and the broken-link search for every link. That's for your own sites and your clients' sites, with their permission.

Can it check faster? Up to two requests per second per host, for a site you run. If your robots.txt sets a Crawl-delay, give sitemap-url-checker its own group with a shorter one.

Why is a URL "outside the sitemap's scope"? A sitemap at https://www.example.com/catalog/sitemap.xml may only list URLs that start with https://www.example.com/catalog/. List the sitemap in your robots.txt (Sitemap: ...) and every URL of that site is in scope.

Why "redirected" when the page works? Search engines prefer sitemaps that list final URLs. The row shows the redirect chain and the final URL to list instead.

What happens if my sitemap is broken? The run succeeds and the site summary says what's wrong, with the rule it breaks. URLs read before an XML error are kept.

Local development

cd sitemap-url-checker
python -m unittest discover -s tests # offline tests with fixtures, standard library only
apify run --input '{"startUrls": ["https://www.example.com/"], "confirmOwnership": true}'

src/core.py, src/sitemap.py, src/page.py and src/robots.py use only the Python standard library; src/main.py wraps them with the Apify SDK (apify==4.0.2).

Support

Report problems in this Actor's Issues tab.