SFOLN

Catalogue / Web toolkit for agents

Sitemap URL Extractor

sfoln/sitemap-url-extractor · Developer tools, SEO tools, Automation On request

Get every URL of a website from its XML sitemaps: robots.txt and /sitemap.xml discovery, sitemap indexes, .gz files, image, news, video and hreflang data. Filter by URL pattern or lastmod date. One JSON row per URL with lastmod, changefreq and priority. $0.20 per 1,000 URLs.

About

What it does

Get every URL of a website from its XML sitemaps as clean JSON. Give a website (the sitemaps are found through robots.txt, then /sitemap.xml and /sitemap_index.xml) or a sitemap file, and you get one row per page URL with lastmod, changefreq, priority, the sitemap it came from and the image, news, video and hreflang data the sitemap carries. Sitemap indexes are followed, .gz files decompressed, broken or cut-off sitemaps read leniently. Built for SEO audits, crawl planning, content inventories, change tracking and AI agents that need the list of pages before they read them. $0.20 per 1,000 URLs; sites without a sitemap cost nothing.

Price

Price per delivered record

EventPricePer 1,000Charged for
Sitemap URL
sitemap-url
$0.0002$0.20 / 1,000Charged once per page URL from a site's sitemaps delivered as a dataset row.

Apify free plan: up to 10 items per run. Paid plans have no per-run cap; set maxTotalChargeUsd to cap spend.

Run

Call it from code, an agent or the Console

This API is private on Apify for now. Email us and we will publish it for you, usually within two working days. The calls below work once it is public. contact@sfoln.com

curl · synchronous run, rows in the response
curl -X POST "https://api.apify.com/v2/actors/sfoln~sitemap-url-extractor/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteUrls":["https://apify.com"],"maxUrlsPerSite":10}'
mcp.json · hosted Apify MCP server
{
  "mcpServers": {
    "apify": {
      "type": "http",
      "url": "https://mcp.apify.com?tools=sfoln/sitemap-url-extractor"
    }
  }
}
asynchronous run · for large batches
POST https://api.apify.com/v2/actors/sfoln~sitemap-url-extractor/runs?maxTotalChargeUsd=1
GET  https://api.apify.com/v2/actor-runs/{runId}            # until status is SUCCEEDED
GET  https://api.apify.com/v2/datasets/{defaultDatasetId}/items?format=json

Authenticate with an Apify API token. MCP clients can instead sign in with OAuth when prompted. Example prompt for an agent: List the first 50 blog URLs from the sitemap of apify.com that changed in the last 30 days.

Input

Input fields

Website or sitemap URLs. Every site gives one dataset row per page URL plus one summary row.

FieldTypeDefaultMeaning
siteUrls *arrayrequiredWebsite or sitemap URLs
A website (its sitemaps are found through robots.txt, then /sitemap.xml and /sitemap_index.xml) or a sitemap file URL (.xml, .xml.gz or .txt; sitemap indexes are followed). One per line. Accounts on the Apify free plan can process up to 10 sites per run.
maxUrlsPerSiteinteger1000Max URLs per site
Page URLs delivered per site (1–1,000,000). Each delivered URL is one sitemap-url event. Range 1–1000000.
includePatternsarray—Include URL patterns
Keep only URLs matching one of these: a glob where * matches anything ("*/blog/*"), a plain substring ("/products/") or a regular expression in slashes ("/\/p\/\d+$/"). Text in slashes is a regular expression only when it uses regex syntax (\ ^ $ ( ) [ ] { } | + ? or .*) or flags such as /i; otherwise it is a substring, slashes included. Globs and substrings are case-insensitive. Empty = all URLs.
excludePatternsarray—Exclude URL patterns
Drop URLs matching one of these (same syntax as the include patterns).
lastmodSincestring—Changed since
Only URLs whose lastmod is on or after this date ("2026-01-01") or within this period ("30 days", "2 weeks", "6 months"). URLs without a lastmod are left out when this is set, and child sitemaps the index dates earlier are not read.
maxSitemapsinteger100Max sitemap files per site
Sitemap files read per site, indexes included (1–1,000). Range 1–1000.
maxDepthinteger3Max index depth
Levels of nested sitemap indexes followed below the start sitemaps (0–5). Range 0–5.
maxSitesinteger1000Max sites
Safety cap on how many URLs are processed in this run; the rest are ignored and counted in the OUTPUT record. Range 1–10000.
concurrencyinteger3Concurrency
Sites processed in parallel (1–10). Requests to one host are always limited to 4 per second. Range 1–10.
Example input
{
  "siteUrls": [
    "https://apify.com"
  ],
  "maxUrlsPerSite": 10
}

Output

One record per input item

Records land in the run's dataset; a synchronous call returns them directly. Each has a status (ok is charged; everything else is free) and a charged flag. The run's OUTPUT record totals items and charges.

FieldTypeMeaning
inputstring | objectInput
The website or sitemap URL this record belongs to, as given.
recordTypestringRecord type
sitemap-url = one page URL (charged); summary = one record per input (never charged). e.g. "sitemap-url"
positionintegerPosition
sitemap-url: 1-based position among the URLs delivered for its input.
siteUrlstring | nullSite
sitemap-url: the input normalized to an absolute URL.
locstring | nullURL
The page URL (sitemap <loc>).
lastmodstring | nullLast modified
ISO 8601 (UTC) of the sitemap's <lastmod>; null when missing or not a valid date.
changefreqstring | nullChange frequency
always | hourly | daily | weekly | monthly | yearly | never, as declared.
prioritynumber | nullPriority
0.0–1.0 as declared.
sitemapstring | nullSitemap
The sitemap file the URL was listed in.
imagesarrayImages
Image extension: [{ loc, title, caption }].
newsobject | nullNews
News extension: { publicationName, language, publicationDate, title, keywords }.
videosarrayVideos
Video extension: [{ title, description, thumbnailLoc, contentLoc, playerLoc, durationSecs, publicationDate, tags }].
alternatesarrayLanguage alternates
hreflang alternates: [{ hreflang, href }].
inputUrlstring | nullInput URL
summary: the input normalized to an absolute URL.
discoveredViastring | nullDiscovered via
summary: input (a sitemap URL was given) | robots (robots.txt Sitemap: lines) | probe (/sitemap.xml or /sitemap_index.xml).
sitemapsReadinteger | nullSitemaps read
summary: sitemap files fetched and read, indexes included.
sitemapsFailedinteger | nullSitemaps failed
summary: sitemap files that could not be fetched or read (see warnings).
sitemapsSkippedinteger | nullSitemaps skipped
summary: child sitemaps not read because of maxDepth, maxSitemaps or lastmodSince.
duplicateUrlsinteger | nullDuplicate URLs
summary: URLs listed more than once (delivered once, never charged twice).
excludedByPatterninteger | nullExcluded by pattern
summary: URLs dropped by includePatterns / excludePatterns.
olderThanLastmodSinceinteger | nullOlder than lastmodSince
summary: URLs whose lastmod is before lastmodSince.
resultsDeliveredinteger | nullURLs delivered
summary: sitemap-url rows delivered (and charged) for this input.
stopReasonstring | nullStop reason
summary: limit (maxUrlsPerSite reached) | end (all sitemaps read) | budget | free_plan | deadline | aborted | error.
statusstringStatus
ok | not_found | error | skipped_invalid | skipped_budget | skipped_free_plan | skipped_unmetered e.g. "ok"
chargedbooleanCharged
true on every sitemap-url row (one sitemap-url event each); summary records are never charged.
chargedEventstringCharged event
sitemap-url on URL rows.
chargedCountintegerEvents charged
Events actually charged when a charge came back short (present only then).
sourcestringSource
Always "sitemap" (fetched from the site by the Actor).
fetchedAtstringFetched at
ISO timestamp.
errorobjectError
{ code, message, provider?, status? } when status is not ok. Codes: not_a_sitemap (no sitemap found or not a sitemap), invalid_url, not_found, http_error, timeout, network, dns_not_found, blocked_url, too_large, unexpected_type, invalid_body, plus the runtime codes budget_exhausted, free_plan_limit, unmetered, aborted, charge_failed, record_too_large, invalid_record, dataset_push_failed.
warningsarrayWarnings
Notes about this record (sitemaps skipped or cut off, URLs without lastmod, caps).
Example record
[
  {
    "input": "https://www.theguardian.com",
    "recordType": "sitemap-url",
    "position": 1,
    "siteUrl": "https://www.theguardian.com/",
    "loc": "https://www.theguardian.com/science/2026/oct/14/example-article",
    "lastmod": "2026-10-14T21:54:00.000Z",
    "changefreq": "hourly",
    "priority": null,
    "sitemap": "https://www.theguardian.com/sitemaps/news.xml",
    "images": [
      {
        "loc": "https://i.guim.co.uk/img/media/example/master/5494.jpg",
        "title": null,
        "caption": null
      }
    ],
    "news": {
      "publicationName": "The Guardian",
      "language": "en",
      "publicationDate": "2026-10-14T21:51:35.000Z",
      "title": "Example science article",
      "keywords": "Science, Space"
    },
    "videos": [],
    "alternates": [],
    "status": "ok",
    "charged": true,
    "chargedEvent": "sitemap-url",
    "source": "sitemap",
    "fetchedAt": "2026-10-14T10:00:00.000Z"
  },
  {
    "input": "https://www.theguardian.com",
    "inputUrl": "https://www.theguardian.com/",
    "discoveredVia": "robots",
    "sitemapsRead": 2,
    "sitemapsFailed": 0,
    "sitemapsSkipped": 0,
    "duplicateUrls": 3,
    "excludedByPattern": 0,
    "olderThanLastmodSince": 0,
    "recordType": "summary",
    "resultsDelivered": 497,
    "stopReason": "end",
    "status": "ok",
    "charged": false,
    "source": "sitemap",
    "fetchedAt": "2026-10-14T10:00:02.000Z"
  }
]

FAQ

Questions

How are the sitemaps found?
For a website URL the Actor reads its robots.txt and uses every Sitemap: line; when there is none it tries /sitemap.xml and /sitemap_index.xml. Sitemap indexes are followed up to maxDepth levels and maxSitemaps files, gzip-compressed sitemaps (.xml.gz) are decompressed, and plain-text sitemaps (one URL per line) and RSS/Atom feeds used as sitemaps are read too. You can also pass a sitemap file URL directly. The summary row says how the sitemaps were found (discoveredVia) and how many files were read.
Can I get only some of the URLs?
Yes: includePatterns and excludePatterns keep or drop URLs by glob ("*/blog/*"), substring or regular expression, and lastmodSince keeps only URLs changed after a date or within a period such as "30 days". Filtered URLs are never charged, and with lastmodSince the Actor also skips child sitemaps its index dates earlier, so large sites are read faster.
What about very large sites?
Each sitemap file is read incrementally (up to 50,000 URLs and 50 MB per file, the limits of the sitemap protocol; less on 256 MB runs), so memory stays flat. Raise maxUrlsPerSite and maxSitemaps for full exports; the run stops cleanly at your maximum cost per run.

Machine-readable

This page for agents