# Wayback Machine Snapshots and Archived Page Content (`codecraftco/wayback-snapshots`) Actor

List every Internet Archive capture of a URL, host or domain from the Wayback Machine CDX index, and optionally pull the archived page text for the latest, earliest or all snapshots.

- **URL**: https://apify.com/codecraftco/wayback-snapshots.md
- **Developed by:** [Code Craft Colorado](https://apify.com/codecraftco) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.40 / 1,000 archived snapshots

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Wayback Machine Snapshots and Archived Page Content

Find out when the Internet Archive captured a page and what the page said at the time. Give
this actor a list of URLs, hosts or whole domains and it returns the matching captures from
the Wayback Machine's CDX index, one row per snapshot, with the capture time, HTTP status,
content type, compressed size, a content digest and two direct links: the browsable Wayback
page and the raw archived copy. Turn on content fetching and it also downloads the archived
page and extracts its visible text, for the latest capture of each URL, the earliest, or all
of them.

It is built to be left running. Every request goes to the Internet Archive's public CDX and
Wayback endpoints over plain HTTP: no API key, no login, no browser, no proxies. The actor
identifies itself with a User-Agent that names the operator and a contact address, spaces its
requests 500 ms apart, works at most two URLs at a time and retries transient errors with
backoff. It reads the index live on every run, so you get whatever the archive holds at that
moment, and one URL failing never stops the others.

### At a glance

Three consecutive daily captures of `apple.com`, from a run with `fetchContent` on and
`contentMode` set to `latest`. Only the newest row carries text; the two newest rows share a
digest, which means the archive stored identical content on both days.

| `capturedAt` | `statusCode` | `length` | `digest` | `contentText` | `waybackUrl` |
| --- | --- | --- | --- | --- | --- |
| `2026-09-26T02:58:15Z` | 200 | 44288 | `7W6JR7MTQOWG76ALW2DPODITZRH5HS56` | `Apple\nApple\nStore\nMac\niPad\niPhone...` | `https://web.archive.org/web/20260926025815/https://www.apple.com/` |
| `2026-09-25T11:53:45Z` | 200 | 44290 | `7W6JR7MTQOWG76ALW2DPODITZRH5HS56` | null | `https://web.archive.org/web/20260925115345/https://www.apple.com/` |
| `2026-09-24T15:31:13Z` | 200 | 44240 | `MGCLIVMS3VKWNIWA7MHYHVQDZLQSAJLG` | null | `https://web.archive.org/web/20260924153113/https://www.apple.com/` |

### What data does it extract?

Every row describes one capture. Field names below are exactly as they appear in the dataset.

**Which page and when**

- `inputUrl`: the URL, host or domain exactly as you gave it.
- `original`: the URL the archive actually captured. With `host` or `domain` matching this
  varies from row to row; even with `exact` it is the archive's canonical form, so an input of
  `apple.com` comes back as `https://www.apple.com/`.
- `timestamp`: the capture time in the archive's 14-digit `YYYYMMDDhhmmss` form, UTC.
- `capturedAt`: the same instant as ISO 8601, for example `2026-09-26T02:58:15Z`. Null only if
  the index returned a malformed timestamp.

**What was captured**

- `mimetype`: the content type recorded at capture time, such as `text/html` or
  `application/pdf`.
- `statusCode`: the HTTP status the archive received when it captured the page. Null when the
  index has no status for the record.
- `digest`: a hash of the captured content. Identical digests mean identical content, so
  comparing digests across rows tells you which captures actually differ.
- `length`: the compressed size of the capture in the archive, in bytes.

**Where to see it**

- `waybackUrl`: the browsable Wayback Machine page for this capture, with the archive's
  toolbar.
- `rawUrl`: the same capture served without the toolbar, using the archive's `id_` form. This
  is what the actor downloads when fetching content.
- `sourceUrl`: the same value as `waybackUrl`, kept for consistency with the other actors in
  this collection.

**The page text, when requested**

- `contentText`: the visible text of the archived page. Null on rows where content was not
  requested or not selected by `contentMode`.
- `contentSkipped`: true when content was requested for this row but not fetched, for example
  a PDF or an image, or a download that failed.
- `contentSkipReason`: why it was skipped, such as `unsupported mimetype application/pdf`, or
  the error message from a failed download.

### Wayback options explained

**`matchType`: what counts as a match**

The value is passed straight to the CDX API's `matchType` parameter.

| Value | What you get |
| --- | --- |
| `exact` | Captures of this one URL, in the archive's canonical form. Default. |
| `prefix` | Captures of this URL and every URL that starts with it, such as everything under `/blog/`. |
| `host` | Captures of every URL on this hostname. |
| `domain` | Captures of every URL on this hostname and all of its subdomains. |

**`collapse`: one capture per period**

Popular pages are captured many times a day. Collapsing asks the index to keep only the first
capture whose timestamp starts with a given number of digits, so consecutive captures within
the same hour, day, month or year fold into one row.

| Value | Timestamp digits compared | Effect |
| --- | --- | --- |
| `none` | none | Every capture the index holds. Default. |
| `hourly` | 10 (`YYYYMMDDhh`) | At most one capture per hour. |
| `daily` | 8 (`YYYYMMDD`) | At most one capture per day. |
| `monthly` | 6 (`YYYYMM`) | At most one capture per month. |
| `yearly` | 4 (`YYYY`) | At most one capture per year. |

The index compares each capture with the one before it, and it is ordered by URL and then by
time, so with `host` or `domain` matching the collapse applies within each URL rather than
across the whole site.

**`timestamp` and `capturedAt`**

The archive addresses every capture by a 14-digit UTC timestamp, `YYYYMMDDhhmmss`; the
capture at `20260926025815` was taken on 26 September 2026 at 02:58:15 UTC. The row keeps
that string as `timestamp`, because it is the key you need to build archive URLs, and adds
`capturedAt` as ISO 8601 for sorting and date arithmetic. The `from` and `to` inputs use the
same digits and accept any prefix from `YYYY` to the full 14.

**`waybackUrl` versus `rawUrl`**

| Field | Shape | Use it for |
| --- | --- | --- |
| `waybackUrl` | `https://web.archive.org/web/{timestamp}/{original}` | Opening in a browser. The archive rewrites links and adds its toolbar. |
| `rawUrl` | `https://web.archive.org/web/{timestamp}id_/{original}` | Programmatic access. The `id_` flag returns the archived bytes as captured, without the toolbar or rewriting. |

**`newestFirst` and `maxSnapshotsPerUrl`**

`maxSnapshotsPerUrl`, when set, caps the rows for each input URL after `from`, `to`,
`onlySuccessful` and `collapse` have been applied. `newestFirst` decides which end of the
history that cap keeps, and the row order either way.

| `newestFirst` | Rows you get | How the index is read |
| --- | --- | --- |
| `true` (default) | The most recent captures, newest row first; the most recent `maxSnapshotsPerUrl` of them when a count is set. | One request per URL when a count is set; otherwise the index is walked and the result reversed. |
| `false` | Oldest row first; the oldest `maxSnapshotsPerUrl` of them when a count is set. | Pages forward through the index with resume keys until the cap is reached or the history ends. |

**`contentMode`: which rows get text**

Only used when `fetchContent` is on. Selection happens among the rows returned for each URL,
so `earliest` with `newestFirst` on means the oldest of the most recent captures, not the
oldest capture in the archive. Set `newestFirst` to false or narrow `from` and `to` if you
want the true first capture.

| Value | Text is fetched for |
| --- | --- |
| `latest` | The single most recent row per input URL. Default. |
| `earliest` | The single oldest row per input URL. |
| `all` | Every row. One download per row, so this is where cost and run time add up. |

**`onlySuccessful`**

On by default, this sends `filter=statuscode:200` to the index so you only see captures where
the archive received a normal page. Turn it off to see redirects (301, 302), errors (404, 500)
and revisit records, which the archive writes when a capture was byte-identical to an earlier
one. Revisit records have no status of their own, so `statusCode` is null on those rows.

### Input

| Field | Meaning | Default | When to change it |
| --- | --- | --- | --- |
| `urls` | URLs, hosts or domains to look up. Required, at least one, no duplicates. | none | Always. |
| `matchType` | `exact`, `prefix`, `host` or `domain`. | `exact` | Surveying a section, a site or a domain family rather than one page. |
| `from` | Earliest capture to include, `YYYY` to `YYYYMMDDhhmmss`. With no dates and no count, the actor returns the last 30 days. | last 30 days | Limiting the history to a window. |
| `to` | Latest capture to include, same format. | unset | Same. |
| `onlySuccessful` | Keep only HTTP 200 captures. | `true` | Turn off to see redirects, errors and revisit records. |
| `collapse` | `none`, `hourly`, `daily`, `monthly` or `yearly`. | `none` | `daily` to thin a busy page; `monthly` or `yearly` for a long, sparse timeline. |
| `maxSnapshotsPerUrl` | Stop after this many captures per URL. Applies only when set, and combines with dates to cap a window. | none | Set to 1 for "the latest capture". |
| `newestFirst` | Take the cap from the recent end of the history. | `true` | Turn off to walk forward from the first capture. |
| `fetchContent` | Download the archived page and add its text. Charged per page fetched. | `false` | Whenever you need what the page said, not just when it was captured. |
| `contentMode` | `latest`, `earliest` or `all`. | `latest` | `all` only when you need every version's text. |

**The latest archived text of a few pages**, one row per URL. This is the saved task
"Latest archived text of a web page"; schedule it to keep a running record.

```json
{
    "urls": ["apple.com", "https://example.com/pricing"],
    "maxSnapshotsPerUrl": 1,
    "fetchContent": true,
    "contentMode": "latest"
}
```

**A change history of one page**, one capture per month since 2020, oldest first, with the
text of the first capture in the window.

```json
{
    "urls": ["https://example.com/pricing"],
    "from": "2020",
    "collapse": "monthly",
    "newestFirst": false,
    "maxSnapshotsPerUrl": 200,
    "fetchContent": true,
    "contentMode": "earliest"
}
```

**Everything the archive holds for a site section**, including redirects and errors, without
content.

```json
{
    "urls": ["https://example.com/blog/"],
    "matchType": "prefix",
    "collapse": "none",
    "onlySuccessful": false,
    "maxSnapshotsPerUrl": 5000
}
```

Listing snapshots costs one request per URL when a count is set with `newestFirst` on, and
a page of index results per few thousand captures otherwise, so a run over a handful of URLs
finishes in seconds. Content is one extra request per selected row, and all
requests are spaced 500 ms apart, so fetching text for every row of a 1,000-row history takes
at least eight to nine minutes plus the archive's own response time.

### Output example

One row from the run shown above; `contentText` is shortened here.

```json
{
    "inputUrl": "apple.com",
    "original": "https://www.apple.com/",
    "timestamp": "20260926025815",
    "capturedAt": "2026-09-26T02:58:15Z",
    "mimetype": "text/html",
    "statusCode": 200,
    "digest": "7W6JR7MTQOWG76ALW2DPODITZRH5HS56",
    "length": 44288,
    "waybackUrl": "https://web.archive.org/web/20260926025815/https://www.apple.com/",
    "rawUrl": "https://web.archive.org/web/20260926025815id_/https://www.apple.com/",
    "contentText": "Apple\nApple\nStore\nMac\niPad\niPhone\nWatch\nVision\nAirPods\nTV & Home\nEntertainment\nAccessories\nSupport\n0\n+\nGet 3 months of Apple One free with any new iPhone, iPad, or Mac.\n1\nLearn more\niPhone 18 Pro\nPro further.\nLearn more\nBuy\n...",
    "contentSkipped": false,
    "contentSkipReason": null,
    "sourceUrl": "https://web.archive.org/web/20260926025815/https://www.apple.com/"
}
```

Rows land in the run's default dataset, which you can view in Console under the Snapshots
tab or download as JSON, CSV, Excel or XML from the dataset's Export menu and the API. Text
extraction drops `script`, `style`, `noscript` and `template` elements, joins the remaining
text with newlines and collapses runs of whitespace, so `contentText` is ready for search,
diffing or a language model without further cleanup.

### Errors and edge cases

Anything that fails is recorded in a JSON record named `ERRORS` in the run's default
key-value store, as a list of `{"url": ..., "error": ...}` objects. The record exists only
when something failed, so its absence means a clean run. The run's status message also
reports the number of errors. Nothing that fails is charged.

- **URL with no captures.** The index returns nothing, the actor writes zero rows for that
  URL and moves on. This is not an error and appears nowhere in `ERRORS`.
- **Index request fails.** On 429 or 5xx responses and network errors the actor retries up to
  four more times with 1, 2, 4 and 8 second waits. If the last attempt fails, or the index
  returns another error immediately, the URL is skipped, an entry goes into `ERRORS`, the other
  URLs continue and no row is charged for it.
- **Content download fails.** The snapshot row is still written and charged as a row, but
  with `contentSkipped` true and the error message in `contentSkipReason`. The `rawUrl` and
  the error also go into `ERRORS`. The `page-content` event is not charged.
- **Binary content.** PDFs, images, archives and anything else that is not `text/*` or XHTML
  are not downloaded. The row has `contentSkipped` true and `contentSkipReason` such as
  `unsupported mimetype application/pdf`. Not charged as a page fetch.
- **Non-200 captures and revisit records.** Hidden by default. With `onlySuccessful` off they
  appear as ordinary rows; `statusCode` is null where the index has no status, as it does for
  revisit records. Content fetching treats them like any other row, so a revisit record
  selected by `contentMode` is skipped as an unsupported type rather than downloaded.
- **Charge limit reached.** If the run hits the maximum charge you set, it stops cleanly and
  keeps every row already written.
- **Empty `urls`.** The run fails immediately with a message and writes nothing.

### How much does it cost?

This actor uses pay-per-event pricing with three events.

- **One dataset row per snapshot.** Every capture returned counts as one row, whether or not
  it carries text.
- **`page-content`, once per page actually fetched.** Charged only when `fetchContent` is on,
  the row was selected by `contentMode`, and text was extracted. Skipped binaries, failed
  downloads and rows outside the `contentMode` selection are not charged.
- **Actor start**, Apify's standard per-run event.

Not charged: URLs with no captures, URLs that failed at the index, retries, the `ERRORS`
record and platform compute, which is included in the event price. The cheapest way to read
a page's history is to list it without content and fetch text only for the rows you need;
`contentMode: "all"` on a long history is where cost grows. Current rates are on the Pricing
tab of this page.

### Use cases

- Find the month a competitor changed its pricing page and read the previous version.
- Recover the text of pages, documentation or announcements that have since gone offline.
- Build a change timeline for a URL by grouping rows on `digest` and keeping the first
  capture of each distinct value.
- Check how often a site or domain is being archived and which of its URLs are covered.
- Keep a running record of a page on a schedule, one new row per day, for compliance or
  research.
- Give an AI agent a tool for questions like "what did this page say in 2021?".

### Run it on a schedule, from the API, or from an agent

Create a schedule in Apify Console to run the actor daily or weekly and read the results
from the dataset, or start runs from the Apify API and the JavaScript and Python clients
with the same input JSON shown above. The actor is also available through Apify's MCP server,
so an agent can call it as a tool. Inputs are identical everywhere, and the saved task
"Latest archived text of a web page" is a ready-made starting point for a scheduled record.

### Related actors

- [SEC Form 4 Insider Trades by Ticker](https://apify.com/codecraftco/sec-insider-trades):
  every insider transaction from Form 4 filings, one row per transaction, with role, price,
  shares, holdings after, and the 10b5-1 flag.
- [SEC 13F Holdings by Fund with Quarterly Changes](https://apify.com/codecraftco/sec-13f-holdings):
  every position in a fund's 13F filing with the change from the prior quarter, in dollars.
- [SEC 8-K Filings by Ticker with Item Codes](https://apify.com/codecraftco/sec-8k-filings):
  8-K filings with item codes and titles, exhibits, and optional extracted text.

All four run against official public APIs with no proxies or credentials.

### FAQ

**Is this legal?** Yes. The Wayback Machine is a public archive operated by the Internet
Archive, and its CDX index and archive URLs are public endpoints. This actor identifies itself
with a User-Agent that names the operator and a contact address, spaces requests 500 ms apart,
works at most two URLs at a time, retries with backoff rather than hammering, and downloads
only the pages you ask for. What you do with archived content of a third party's site is
governed by that content's copyright as it would be if you read it in a browser.

**Why do I get fewer rows than expected?** Three inputs thin the history: `collapse` keeps
one capture per period when set, `onlySuccessful` drops redirects, errors and revisit
records, and with no dates and no count only the last 30 days are returned. Set `from`, turn
`onlySuccessful` off and leave `collapse` at `none` to see everything the archive holds.

**Why is `original` different from the URL I entered?** The index matches on the archive's
canonical form of a URL, so `apple.com` matches captures stored as `https://www.apple.com/`.
`inputUrl` always keeps what you typed; `original` tells you what was actually captured.

**Why is `contentText` null on most rows?** Text is fetched only for the rows selected by
`contentMode`, which by default is the latest capture per URL. Other rows keep
`contentSkipped` false and `contentText` null because content was never requested for them.
Rows where content was requested but could not be fetched have `contentSkipped` true and a
reason.

**Can I get the archived HTML instead of the text?** Not from this actor; it returns
extracted text. Every row carries `rawUrl`, which serves the archived bytes exactly as
captured, so you can download the HTML yourself for any capture you care about.

**Does it save new pages to the archive?** No. It only reads the index and existing
captures. It never asks the Wayback Machine to capture anything.

**How do I tell which captures actually changed?** Compare `digest`. In the sample above the
captures of 26 and 25 September share a digest, so the page was byte-identical on both days,
while the 24 September capture differs.

**Do I need an API key, a proxy or a browser?** No. All requests are plain HTTP to the
Internet Archive's public endpoints from Apify's own infrastructure.

**What happens when the Wayback Machine is slow or returns errors?** Requests use a 60 second
timeout and retry on 429 and 5xx responses and network failures, five attempts in total with
exponential backoff. A URL that still fails is recorded in `ERRORS`, not charged, and does not
stop the rest of the run.

**Something is missing or wrong?** Open an issue on this actor's Issues tab with the input you
used and the run ID, and it will be looked at.

# Actor input Schema

## `urls` (type: `array`):

URLs, hosts or domains to look up in the Wayback Machine, e.g. example.com or https://example.com/pricing. How each one is matched is controlled by Match type.

## `matchType` (type: `string`):

exact = only this URL; prefix = this URL and everything under its path; host = every URL on this host; domain = the host and all its subdomains.

## `from` (type: `string`):

Earliest capture to include, as a timestamp prefix from YYYY to YYYYMMDDhhmmss. If you set no dates and no count, the actor returns captures from the last 30 days.

## `to` (type: `string`):

Latest capture to include, same format as From.

## `onlySuccessful` (type: `boolean`):

Skip captures of redirects, errors and missing pages.

## `collapse` (type: `string`):

Keep at most one capture per period. `none` returns every capture the index holds.

## `maxSnapshotsPerUrl` (type: `integer`):

Stop after this many captures per URL. Only applies when set; combine with dates to cap a window.

## `newestFirst` (type: `boolean`):

Return the most recent captures. Turn off to walk forward from the oldest capture instead.

## `fetchContent` (type: `boolean`):

Download the archived page and add its visible text to the row. Charged per page fetched.

## `contentMode` (type: `string`):

Only used when Fetch archived page content is on.

## Actor input object example

```json
{
  "urls": [
    "example.com"
  ],
  "matchType": "exact",
  "onlySuccessful": true,
  "collapse": "none",
  "newestFirst": true,
  "fetchContent": false,
  "contentMode": "latest"
}
```

# Actor output Schema

## `snapshots` (type: `string`):

One row per Wayback Machine capture: timestamp, archive and raw URLs, status, content type, and optional extracted page text.

## `errors` (type: `string`):

Inputs that could not be processed, with the reason. Present only when something failed; no row is charged for a failure.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "example.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("codecraftco/wayback-snapshots").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "urls": ["example.com"] }

# Run the Actor and wait for it to finish
run = client.actor("codecraftco/wayback-snapshots").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "example.com"
  ]
}' |
apify call codecraftco/wayback-snapshots --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,codecraftco/wayback-snapshots"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/tR6hLN5R0PV9h0Syb/builds/iKlZtFR4uvyvg1A32/openapi.json
