Wayback Machine Snapshots: Archived URL History & Dead Links avatar

Wayback Machine Snapshots: Archived URL History & Dead Links

Pricing

from $0.70 / 1,000 archived captures

Go to Apify Store
Wayback Machine Snapshots: Archived URL History & Dead Links

Wayback Machine Snapshots: Archived URL History & Dead Links

Every archived capture of a URL from the Internet Archive Wayback Machine: capture time in UTC, HTTP status, MIME type, size, content digest, replay link and raw-file link. Also finds the copy closest to a date and lists the archived URLs of a whole site.

Pricing

from $0.70 / 1,000 archived captures

Rating

0.0

(0)

Developer

Samat Makatov

Samat Makatov

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

4 days ago

Last modified

Share

Look up any address in the Internet Archive's Wayback Machine capture index and get one row per stored capture: capture time in UTC, the HTTP status the crawler saw, content type, stored size, the content hash, a link to the archived copy and a raw link that returns the stored file itself. The same actor answers two neighbouring questions: which single archived copy sits closest to a date you name (the one a dead footnote needs), and which addresses under a site, path or domain are in the archive at all. No API key, no account, no proxy and no browser — it reads the archive's public index and returns links, never the archived bytes.

Need it tailored to your workflow? Missing a field, a filter, another source or a different output format? Open an issue and describe what you need — we read every request and adapt the actor.

Use cases

  • Repair a reference list. Send 80 URLs in closest mode with the date you read them, get one citable archived copy per URL and a found: false row for the few the archive never stored.
  • Date a wording change. Run captures with changedOnly on a pricing, policy or terms page: hundreds of captures collapse to the handful of moments the stored content really changed.
  • Recover files a site removed. siteUrls with mimeTypes set to application/pdf, audio/ or image/ lists the archived documents, recordings or pictures under a path, each with a raw-file link.
  • Audit a migrated domain. siteUrls with matchScope: "domain" shows which addresses — subdomains included — the archive holds for a host, which is the list of URLs an SEO migration has to account for.
  • Prove a page existed on a date. One capture row with its UTC time, status and hash is evidence you can link to.
  • Check a domain's age and early look. captures with collapseBy: "year" puts the oldest capture in the first row and one copy per year after it.
  • Watch your own pages. sinceHours plus onlyNew on a schedule reports only captures stored since the last run.

Input

FieldTypeDefaultWhat it does
modestring (enum)capturesWhich question to answer: captures (one row per stored capture of each URL), closest (one row per URL: the capture nearest targetDate), siteUrls (one row per archived address under a site). See Modes.
urlsstring[]required (prefill https://www.bbc.com/news, https://en.wikipedia.org/wiki/Internet_Archive)The addresses to look up, at most 100 per run. https://example.com/page, example.com/page and http://example.com/page are all accepted. A trailing slash matters for siteUrls with prefix scope.
targetDatestringprefill 2016-03-01closest only: the moment you want a copy from. 2016, 2016-03, 2016-03-01 or 2016-03-01T12:00:00Z. Empty = the newest capture.
matchScopestring (enum)prefixsiteUrls only: prefix (everything under the path), host (that host), domain (host plus subdomains). See Site scope.
fromDatestringprefill 2024-01-01Earliest capture to return (UTC). 2024, 2024-01 or 2024-01-01; a year or month alone starts at its first moment.
toDatestringprefill 2024-12-31Latest capture to return. Same forms; a year, month or day alone covers the whole period. Both empty = the full history back to 1996.
sinceHoursnumberemptyA moving window in hours (1–8760). While it is set it replaces fromDate, and the run says so in its status.
onlyNewbooleanfalseRemember the captures this run wrote and skip them next time (keyed by archived address + capture timestamp). Made for schedules.
statusCodesstring[]prefill 200Keep only captures whose stored status is one of these. Empty = every capture. See Status values.
mimeTypesstring[]emptyKeep only these content types; a family prefix such as audio/ keeps the whole family. Empty = every type. See Content types.
collapseBystring (enum)dayThin the history inside the index: none, hour, day, month, year, or digest (drop a capture repeating the previous hash). See Thinning.
changedOnlybooleanfalsecaptures only: keep a capture only when its content hash differs from the capture before it. The first capture is always kept.
newestFirstbooleanfalseAsk for the end of the history instead of the beginning, so a small row cap shows the most recent captures.
maxItemsPerUrlnumber25At most this many rows per URL (1–2000). Sent to the index with the query.
maxItemsnumber50Hard cap on the rows the run writes, and therefore on what it costs (1–2000).
fieldsstring[]emptyKeep only these columns, in this order. queryUrl, originalUrl, found, sourceMode, url and fetchedAt are always kept — the ones you do not list come first, and one you do list stays where you put it. A name that is not a column is reported in the run's status message.

mode, matchScope and collapseBy are strict dropdowns: the platform refuses a value that is not on the list with HTTP 400 before the run starts, and names the allowed values. statusCodes and mimeTypes are free text, so they forgive how you type them — see their dictionaries below.

Reference

Modes

modeOne row isUsesIndex requests per URL
capturesone stored capture of the URL you asked forfromDate, toDate, sinceHours, collapseBy, changedOnly, newestFirst, statusCodes, mimeTypes1
closestthe capture nearest targetDate, one per URLtargetDate, statusCodes, mimeTypes2 (1 without targetDate)
siteUrlsone archived address under the sitematchScope, fromDate, toDate, statusCodes, mimeTypes1

In closest mode add statusCodes: ["200"] when you need a copy with content — the nearest capture is otherwise sometimes a redirect or an error page.

maxItemsPerUrl caps the rows one URL may contribute; maxItems caps the whole run. With 20 URLs and maxItemsPerUrl: 25 the run writes at most 500 rows — set maxItems to what you are willing to pay for.

Site scope

  • prefix — the path you typed and everything under it: https://www.irs.gov/pub/irs-pdf/ returns that folder.
  • host — every archived address on exactly that host, whatever path you typed.
  • domain — the host and all of its subdomains, which is how forgotten old. and files. hosts turn up.

Status values

The status is what the crawler received at capture time: 200 content, 301/302 a redirect, 404 a page already gone, 403 a refusal, 503 an outage. Deduplicated revisit records carry no status: the index stores - for them and this actor writes an empty statusCode column — add - to the list when you want those rows too.

Any three-digit code is accepted as typed. Words are read as well (ok → 200, not found → 404, gone → 410, forbidden → 403, revisit → -), and case, spaces, underscores and hyphens do not matter. Anything else is refused before the run does any work, with the closest values named: statusCodes: ["20O"] comes back as Unknown statusCodes "20O" (did you mean "200"?). Nothing is silently substituted.

Content types

Common values in the index: text/html, text/plain, application/pdf, application/json, application/zip, image/jpeg, image/png, image/gif, image/svg+xml, audio/mpeg, audio/x-wav, video/mp4, text/css, application/javascript, warc/revisit (a deduplicated record).

A value ending in / is a family: audio/ keeps every audio type, image/ every picture, application/ every document or data file. Short names are read too (pdf, html, jpg, mp3, mp4, csv, zip, audio, image, video), as are case and spacing variants. A value whose type is not a real content type is refused with a suggestion: aplication/pdf comes back as Unknown mimeTypes "aplication/pdf" (did you mean "application/pdf"?).

Thinning

collapseBy is applied by the index before rows are counted, so it changes which captures you see, not how many rows you pay for. day (the default) keeps at most one capture per calendar day — a news front page archived 40 times a day becomes 365 rows a year instead of ~15000. hour, month and year work the same way at their period, none returns the complete record, and digest drops a capture whose content hash equals the one before it.

changedOnly is our own comparison on top of that: it scans up to 20× your maxItemsPerUrl (at most 2000 captures) of the URL's history and keeps only the captures whose hash differs from the previous one.

Examples

Which copies of a news page exist for 2024 — one capture per month, content only.

{
"mode": "captures",
"urls": ["https://www.bbc.com/news"],
"fromDate": "2024-01-01",
"toDate": "2024-12-31",
"collapseBy": "month",
"statusCodes": ["200"],
"maxItemsPerUrl": 12,
"maxItems": 12
}

An archived copy of each dead link, near the date it was read — one row per URL, found: false where nothing exists.

{
"mode": "closest",
"urls": ["https://www.bbc.com/news", "https://en.wikipedia.org/wiki/Internet_Archive", "https://www.nasa.gov/"],
"targetDate": "2016-03-01",
"maxItems": 5
}

Archived PDF files under a path, each with a raw-file link that returns the stored document.

{
"mode": "siteUrls",
"urls": ["https://www.irs.gov/pub/irs-pdf/"],
"matchScope": "prefix",
"mimeTypes": ["application/pdf"],
"maxItemsPerUrl": 20,
"maxItems": 20
}

When did this page change — only captures whose stored content hash differs from the one before.

{
"mode": "captures",
"urls": ["https://en.wikipedia.org/wiki/Internet_Archive"],
"fromDate": "2023-01-01",
"toDate": "2024-12-31",
"collapseBy": "day",
"changedOnly": true,
"statusCodes": ["200"],
"maxItemsPerUrl": 15,
"maxItems": 15
}

Archived addresses of a whole domain, subdomains included.

{
"mode": "siteUrls",
"urls": ["https://news.ycombinator.com/"],
"matchScope": "domain",
"statusCodes": ["200"],
"maxItemsPerUrl": 25,
"maxItems": 25
}

New captures of a page in the last 90 days, newest first — schedule it with onlyNew: true.

{
"mode": "captures",
"urls": ["https://www.gov.uk/"],
"sinceHours": 2160,
"newestFirst": true,
"collapseBy": "day",
"maxItemsPerUrl": 15,
"maxItems": 15
}

Output

One real row from a cloud run of 06.10.2026 (captures mode, the 2024 window above):

{
"queryUrl": "https://www.bbc.com/news",
"originalUrl": "https://www.bbc.com/news",
"urlKey": "com,bbc)/news",
"timestamp": "20240101001625",
"capturedAt": "2024-01-01T00:16:25Z",
"statusCode": 200,
"mimeType": "text/html",
"digest": "MNUDJY5ILQTZ7FVLKOYEJ3NUGOS6Q5BT",
"lengthBytes": 50543,
"url": "https://web.archive.org/web/20240101001625/https://www.bbc.com/news",
"rawUrl": "https://web.archive.org/web/20240101001625id_/https://www.bbc.com/news",
"found": true,
"sourceMode": "captures",
"fetchedAt": "2026-10-06T21:50:40.024Z"
}
FieldTypeMeaning
queryUrlstringThe address you sent, unchanged
originalUrlstringThe address as the archive stores it (its canonical form, often with the old :80 port)
urlKeystringThe index's sort key for that address (com,bbc)/news)
timestampstringThe archive's own 14-digit capture time, YYYYMMDDhhmmss UTC
capturedAtstringThe same moment as ISO-8601 UTC
statusCodenumberHTTP status at capture time; empty for a deduplicated revisit record (the index stores -)
mimeTypestringContent type at capture time; warc/revisit on a deduplicated record
digeststringBase-32 SHA-1 of the stored body: equal digests mean byte-identical copies
lengthBytesnumberSize of the stored record in bytes (compressed), not the page's own size
urlstringLink to the archived copy as the Wayback Machine replays it
rawUrlstringLink to the stored bytes themselves (id_ form) — the one to use for a PDF, image or audio file
changedFromPreviousbooleancaptures mode: does this capture's hash differ from the previous capture's? Empty on the first capture of the slice
captureCountnumbersiteUrls mode: how many captures the index reports for that address — usually empty, the index returns no count today
targetDatestringclosest mode: the date you asked for, as you wrote it
offsetDaysnumberclosest mode: days between the capture and your date; negative = the capture is older
notFoundReasonstringOnly on a found: false row: why there is no capture (nothing archived, or the filters matched none)
foundbooleantrue on a capture row, false on a marker row for an address the archive has nothing for
sourceModestringWhich question produced the row: captures, closest or siteUrls
fetchedAtstringWhen this run read the index, ISO-8601 UTC

Columns a mode cannot fill are left out of its rows instead of being written empty, so a captures row has no offsetDays and a closest row no changedFromPrevious.

Dataset views: Archived captures (the wide table), History of one page (timeline of one address with hashes and the changed flag), Closest copy per URL (one line per URL with the distance from your date).

Use it from code / agents

curl -X POST "https://api.apify.com/v2/acts/yadroo~wayback-machine-snapshots/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"mode":"closest","urls":["https://www.bbc.com/news"],"targetDate":"2016-03-01","maxItems":5}'
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('yadroo/wayback-machine-snapshots').call({
mode: 'captures',
urls: ['https://en.wikipedia.org/wiki/Internet_Archive'],
fromDate: '2024-01-01',
toDate: '2024-12-31',
maxItems: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("yadroo/wayback-machine-snapshots").call(run_input={
"mode": "siteUrls",
"urls": ["https://www.irs.gov/pub/irs-pdf/"],
"mimeTypes": ["application/pdf"],
"maxItems": 20,
})
items = client.dataset(run["defaultDatasetId"]).list_items().items

The run's key-value store holds a SUMMARY record with the mode, how many URLs were looked up, how many index requests it took, how many URLs came back as not found, how many rows were skipped as already delivered, and whether the run stopped at your cost cap or before the run timeout.

MCP: add https://mcp.apify.com to Claude / Cursor / any MCP client and call the yadroo/wayback-machine-snapshots tool with the same JSON input. For "find me a readable copy of this dead link", send {"mode":"closest","urls":["<the dead URL>"],"targetDate":"<when you saw it>","maxItems":1} and read url (the replay page) or rawUrl (the stored file) from the single row; found: false means the archive has nothing and notFoundReason says why.

Pricing

Pay per event: $0.001 per run start + $0.001 per dataset row. Every run pays the start event, including runs that write no rows, and every row written to the dataset is charged — a found: false marker row for an address the archive does not hold is a row and is billed, because it is the answer you asked for.

  • The default input (2 URLs, one capture per day of 2024, maxItems: 50) → 50 rows: 0.001 + 50 × 0.001 = $0.051.
  • A reference list of 80 URLs in closest mode → 80 rows: 0.001 + 80 × 0.001 = $0.081.
  • One archived PDF folder, maxItems: 20 → 20 rows: 0.001 + 20 × 0.001 = $0.021.

maxItems is the cost switch: the run stops as soon as it is reached. The run also respects the Maximum cost per run you set on the platform — when that cap is reached the run stops, keeps and delivers the rows it already paid for, and ends with Stopped at your spending limit: N rows delivered.

Limits & FAQ

  • Measured speed (06.10.2026, Apify cloud, 256 MB). The actor keeps one request per second to web.archive.org through a single token bucket, retries included, and the index itself answers in 1–5 s.
    • captures and siteUrls need one index request per URL: the default input (2 URLs, 50 rows) finished in 10–20 s, a 25-row whole-domain listing of news.ycombinator.com in 32 s. Roughly 25–40 URLs a minute, so the 100-URL maximum fits the default 900 s run timeout comfortably.
    • closest needs two requests per URL (the last capture before your date and the first one after it) and the second one is slow on a heavily archived page. A 30-URL run took 59 index requests in 268 s — about 9 s per URL. Keep closest batches at 60 URLs or fewer per run, or raise the run timeout; a batch that does not fit stops early, keeps every row it has read and says so.
  • The archive's rate limit is not published as a number. The Internet Archive says it has put protections in place against high-volume automated traffic and answers 429 when a client is over the line. 59 requests in a row at one per second were served without a single 429 or block on 06.10.2026, which is the pace this actor keeps. It honours Retry-After and retries a 429, a 5xx or the archive's "Temporarily Offline" page with growing waits; when the archive keeps refusing three URLs in a row the run stops and says so instead of hammering it — start it again in a few minutes. A larger run simply takes longer; it is never faster than one request per second.
  • A wide scope plus a filter can be more than one index query answers. siteUrls with host or domain scope over a large site, combined with a content-type or status filter, makes the index scan millions of entries and it can answer with a gateway timeout. That URL comes back as a failed lookup naming the fix: narrow the scope to prefix, or add a fromDate/toDate window.
  • A run can stop before its timeout. Near the run timeout, and at your maximum cost per run, the actor stops starting new work, keeps every row it has read and ends successfully with a status that says why (
    Stopped before the run timeout: N rows saved
    , Stopped at your spending limit: N rows delivered). Rows are written as each URL finishes, so an interrupted run never loses what it read. Verified in the cloud: a 40-URL closest run with a 60 s run timeout ended SUCCEEDED with its rows and that status.
  • Coverage is what the archive's crawlers saw. A small or regional site may have a handful of captures a year, or none; pages the archive excludes, or pages that were blocked when the crawler came, are missing. A capture can also be a redirect, a cookie wall or an error page — read statusCode and lengthBytes before quoting it.
  • Some addresses are refused by the index itself. For a few addresses the index answers 403 (excluded or withheld) or 404. That URL gets a row with found: false and the reason instead of disappearing from the dataset, so a reference list always comes back complete. It is a billed row like any other.
  • digest is a byte hash, not an editorial diff. A rotated advert or a changed banner changes the hash, so treat changedFromPrevious as "worth looking at", not as proof of an edit.
  • One index query answers at most about 10000 rows. A whole-domain export is therefore done in slices: by year with fromDate/toDate, or by path with prefix scope.
  • captureCount is usually empty. The index accepts the request for a per-address capture count but returns no value for it today; we ask for it anyway and leave the column empty rather than spend an extra request per address.
  • It never writes to the archive. Saving a new capture ("Save Page Now") needs an archive account and is out of scope, as are downloading, rendering or re-publishing archived content: every row is a link plus metadata.
  • Empty results vs errors. A URL with no matching capture always produces a row with found: false and a reason, so a list of 80 references can be checked in one pass; an unreadable input (a date we cannot parse, a filter value we cannot match, more than 100 URLs) fails the run before any request, naming the one thing to change.

Questions or problems: open an issue on the Issues tab of this actor.


Made by Yadroo. Sibling actors: domain-intel, rss-to-json, wikipedia-search, github-repo-intel, data-gov-datasets.