Wayback Machine Snapshots: Archived URL History & Dead Links
Pricing
from $0.70 / 1,000 archived captures
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
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
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
closestmode with the date you read them, get one citable archived copy per URL and afound: falserow for the few the archive never stored. - Date a wording change. Run
captureswithchangedOnlyon 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.
siteUrlswithmimeTypesset toapplication/pdf,audio/orimage/lists the archived documents, recordings or pictures under a path, each with a raw-file link. - Audit a migrated domain.
siteUrlswithmatchScope: "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.
captureswithcollapseBy: "year"puts the oldest capture in the first row and one copy per year after it. - Watch your own pages.
sinceHoursplusonlyNewon a schedule reports only captures stored since the last run.
Input
| Field | Type | Default | What it does |
|---|---|---|---|
mode | string (enum) | captures | Which 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. |
urls | string[] | 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. |
targetDate | string | prefill 2016-03-01 | closest only: the moment you want a copy from. 2016, 2016-03, 2016-03-01 or 2016-03-01T12:00:00Z. Empty = the newest capture. |
matchScope | string (enum) | prefix | siteUrls only: prefix (everything under the path), host (that host), domain (host plus subdomains). See Site scope. |
fromDate | string | prefill 2024-01-01 | Earliest capture to return (UTC). 2024, 2024-01 or 2024-01-01; a year or month alone starts at its first moment. |
toDate | string | prefill 2024-12-31 | Latest capture to return. Same forms; a year, month or day alone covers the whole period. Both empty = the full history back to 1996. |
sinceHours | number | empty | A moving window in hours (1–8760). While it is set it replaces fromDate, and the run says so in its status. |
onlyNew | boolean | false | Remember the captures this run wrote and skip them next time (keyed by archived address + capture timestamp). Made for schedules. |
statusCodes | string[] | prefill 200 | Keep only captures whose stored status is one of these. Empty = every capture. See Status values. |
mimeTypes | string[] | empty | Keep only these content types; a family prefix such as audio/ keeps the whole family. Empty = every type. See Content types. |
collapseBy | string (enum) | day | Thin the history inside the index: none, hour, day, month, year, or digest (drop a capture repeating the previous hash). See Thinning. |
changedOnly | boolean | false | captures only: keep a capture only when its content hash differs from the capture before it. The first capture is always kept. |
newestFirst | boolean | false | Ask for the end of the history instead of the beginning, so a small row cap shows the most recent captures. |
maxItemsPerUrl | number | 25 | At most this many rows per URL (1–2000). Sent to the index with the query. |
maxItems | number | 50 | Hard cap on the rows the run writes, and therefore on what it costs (1–2000). |
fields | string[] | empty | Keep 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
mode | One row is | Uses | Index requests per URL |
|---|---|---|---|
captures | one stored capture of the URL you asked for | fromDate, toDate, sinceHours, collapseBy, changedOnly, newestFirst, statusCodes, mimeTypes | 1 |
closest | the capture nearest targetDate, one per URL | targetDate, statusCodes, mimeTypes | 2 (1 without targetDate) |
siteUrls | one archived address under the site | matchScope, fromDate, toDate, statusCodes, mimeTypes | 1 |
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 forgottenold.andfiles.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"}
| Field | Type | Meaning |
|---|---|---|
queryUrl | string | The address you sent, unchanged |
originalUrl | string | The address as the archive stores it (its canonical form, often with the old :80 port) |
urlKey | string | The index's sort key for that address (com,bbc)/news) |
timestamp | string | The archive's own 14-digit capture time, YYYYMMDDhhmmss UTC |
capturedAt | string | The same moment as ISO-8601 UTC |
statusCode | number | HTTP status at capture time; empty for a deduplicated revisit record (the index stores -) |
mimeType | string | Content type at capture time; warc/revisit on a deduplicated record |
digest | string | Base-32 SHA-1 of the stored body: equal digests mean byte-identical copies |
lengthBytes | number | Size of the stored record in bytes (compressed), not the page's own size |
url | string | Link to the archived copy as the Wayback Machine replays it |
rawUrl | string | Link to the stored bytes themselves (id_ form) — the one to use for a PDF, image or audio file |
changedFromPrevious | boolean | captures mode: does this capture's hash differ from the previous capture's? Empty on the first capture of the slice |
captureCount | number | siteUrls mode: how many captures the index reports for that address — usually empty, the index returns no count today |
targetDate | string | closest mode: the date you asked for, as you wrote it |
offsetDays | number | closest mode: days between the capture and your date; negative = the capture is older |
notFoundReason | string | Only on a found: false row: why there is no capture (nothing archived, or the filters matched none) |
found | boolean | true on a capture row, false on a marker row for an address the archive has nothing for |
sourceMode | string | Which question produced the row: captures, closest or siteUrls |
fetchedAt | string | When 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 ApifyClientclient = 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
closestmode → 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.orgthrough a single token bucket, retries included, and the index itself answers in 1–5 s.capturesandsiteUrlsneed one index request per URL: the default input (2 URLs, 50 rows) finished in 10–20 s, a 25-row whole-domain listing ofnews.ycombinator.comin 32 s. Roughly 25–40 URLs a minute, so the 100-URL maximum fits the default 900 s run timeout comfortably.closestneeds 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. Keepclosestbatches 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
429when a client is over the line. 59 requests in a row at one per second were served without a single429or block on 06.10.2026, which is the pace this actor keeps. It honoursRetry-Afterand retries a429, a5xxor 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.
siteUrlswithhostordomainscope 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 toprefix, or add afromDate/toDatewindow. - 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-URLclosestrun 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
statusCodeandlengthBytesbefore quoting it. - Some addresses are refused by the index itself. For a few addresses the index answers
403(excluded or withheld) or404. That URL gets a row withfound: falseand the reason instead of disappearing from the dataset, so a reference list always comes back complete. It is a billed row like any other. digestis a byte hash, not an editorial diff. A rotated advert or a changed banner changes the hash, so treatchangedFromPreviousas "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 withprefixscope. captureCountis 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: falseand 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.