GuidesWAYBACK MACHINE

Wayback Machine API from JavaScript

Prefix https://proxy.cors.dev/ to web.archive.org CDX and capture URLs: free, no signup, no key, 600 requests per minute, responses up to 1 MiB. The CDX API and archived pages send no Access-Control-Allow-Origin header, so fetch() from your page fails with a CORS error. The availability API at archive.org/wayback/available, the archive.org search API and the metadata API do send Access-Control-Allow-Origin: *, so call those three directly. Every endpoint and size below was tested in October 2026.

Which Wayback Machine endpoints need a proxy

The Internet Archive splits its APIs across two hosts with different CORS rules. archive.org answers with Access-Control-Allow-Origin: *. web.archive.org, home of the CDX index and every capture, sends no CORS header, so the browser receives the response and refuses to hand it to your script. Prefixing those URLs with https://proxy.cors.dev/ fetches them from a server and returns them with the header. Reading needs no key anywhere.

Wayback Machine and archive.org endpoints from a web page, checked October 2026
RequestReturnsSizeDirect from a pageThrough cors.dev
archive.org/wayback/available?url=example.comClosest capture: URL, timestamp, status203 BWorks, sends *Not needed
web.archive.org/wayback/availableNothing: wrong host-404, no header404 passed through
web.archive.org/cdx/search/cdx?url=example.com&output=json&limit=3Header row and 3 captures447 BBlocked, no header200
cdx with fl, filter, collapse=timestamp:4, from=2015, to=2020One capture per year, 3 columns352 BBlocked, no header200
cdx with limit=-1Latest capture only201 BBlocked, no header200
cdx without limitEvery capture of example.comOver 1 MiBBlocked, no header502 response_too_large
cdx with matchType=prefix or domainEvery URL under a path or hostNo answer in 40 sBlocked, no header503 or 504 upstream_timeout
web.archive.org/web/timemap/json?url=example.com&limit=3The same rows as CDX447 BBlocked, no header200
web.archive.org/web/20200101000000/https://example.com/Replay page with Wayback scripts2.9 KBBlocked, no header200 after 1 redirect
web.archive.org/web/20200101000000id_/https://example.com/Original HTML as captured1.3 KBBlocked, no header200 after 1 redirect
id_ capture of en.wikipedia.org/wiki/Main_PageOriginal HTML74 KBBlocked, no header200
id_ capture of www.bbc.comOriginal HTML229 KBBlocked, no header200
id_ capture of edition.cnn.comOriginal HTML1.1 MBBlocked, no header502 response_too_large
im_ capture of a PNGImage bytes-Blocked, no header502 unsupported_response
archive.org/advancedsearch.php?q=collection:nasa&rows=5&output=json5 identifiers and titles741 BWorks, sends *Not needed
archive.org/services/search/v1/scrape?q=collection:nasa&count=100100 results and a cursor8.7 KBWorks, sends *Not needed
archive.org/metadata/nasaCollection metadata and files6.7 KBWorks, sends *Not needed
archive.org/metadata/principleofrelat00einsScanned book: metadata and 27 files56 KBWorks, sends *Not needed

Check if a URL is archived

https://archive.org/wayback/available?url=example.com returns the capture closest to now in about 200 B; add &timestamp=20060101 for the one closest to a date. archived_snapshots.closest holds available, url, timestamp and status, and archived_snapshots is empty when the URL was never archived.

Closest capture of a URL, called directly
// Markup: <p id="capture"></p>
// No proxy: archive.org/wayback/available sends Access-Control-Allow-Origin: *
async function closestCapture(url, timestamp) {
  const query = new URLSearchParams({ url });
  if (timestamp) query.set('timestamp', timestamp);
  const response = await fetch('https://archive.org/wayback/available?' + query, {
    credentials: 'omit',
  });
  if (!response.ok) throw new Error(`Availability check failed: ${response.status}`);
  const { archived_snapshots: snapshots } = await response.json();
  if (!snapshots.closest) return null;
  return {
    timestamp: snapshots.closest.timestamp,
    url: snapshots.closest.url.replace(/^http:/, 'https:'),
  };
}

const output = document.querySelector('#capture');
closestCapture('example.com', '20060101')
  .then((capture) => {
    output.textContent = capture ? `${capture.timestamp}: ${capture.url}` : 'Not archived';
  })
  .catch((error) => {
    output.textContent = error.message;
  });

The url field starts with http:// even for https sites, so the example switches it before you link to it. On web.archive.org the same path is a 404. archive.org sends Cache-Control: max-age=21600, so the browser reuses an answer for up to 6 hours.

Wayback CDX API: every capture of a URL

The CDX server at https://web.archive.org/cdx/search/cdx lists captures, one row each. With output=json, the first row holds the column names: urlkey, timestamp, original, mimetype, statuscode, digest and length. It sends no CORS header, so the example prefixes it.

Captures of a URL from the CDX API
// Top-level await: run it in a module script or the DevTools console
const CDX = 'https://proxy.cors.dev/https://web.archive.org/cdx/search/cdx';

async function captures(url, options = {}) {
  const query = new URLSearchParams({ url, output: 'json', limit: '100', ...options });
  const response = await fetch(`${CDX}?${query}`, { credentials: 'omit' });
  if (!response.ok) {
    throw new Error(`CDX request failed: ${response.headers.get('X-Cors-Error') ?? response.status}`);
  }
  const [fields, ...rows] = await response.json();
  if (!fields) return [];
  return rows
    .filter((row) => row.length === fields.length)
    .map((row) => Object.fromEntries(fields.map((field, index) => [field, row[index]])));
}

const yearly = await captures('example.com', {
  fl: 'timestamp,original,statuscode',
  filter: 'statuscode:200',
  collapse: 'timestamp:4',
  from: '2015',
  to: '2020',
});
console.table(yearly);

  • url: the page to look up; matchType=prefix, host or domain, or a trailing /*, widens it to a path or host.
  • from and to: timestamps such as 2015 or 20150301.
  • fl: the columns to return, such as timestamp,original,statuscode.
  • filter: field:regex, repeatable; ! before the field excludes matches, as in filter=!statuscode:200.
  • collapse: drops adjacent rows with the same value. collapse=digest skips captures whose content did not change and collapse=timestamp:4 keeps one per year.
  • limit: maximum rows; limit=-1 returns only the latest capture (201 B). Always set it: an unlimited query for example.com passes 1 MiB and the proxy answers 502 response_too_large.
  • showResumeKey=true: appends a resume key; pass it back as resumeKey for the next batch.

Wide queries are slow. url=paulgraham.com/* took 37 seconds directly, and through the proxy prefix and domain queries for example.com ended in 503 or a 504 upstream_timeout after the free tier's 10 seconds. Even a single-URL query timed out once in two tries, so narrow with from, to and limit and retry a 504 once.

Fetch archived HTML with id_

A capture URL is https://web.archive.org/web/<timestamp>/<url>. Requested as is, it redirects to the nearest capture and returns the replay version, with links rewritten to web.archive.org and Wayback's bundle-playback.js and wombat.js scripts added: 2.9 KB for example.com instead of 1.3 KB. Add id_ right after the timestamp to get the page exactly as captured.

Original HTML of a capture with id_
// Markup: <p id="archived"></p>
async function archivedPage(timestamp, url) {
  const snapshot = `https://web.archive.org/web/${timestamp}id_/${url}`;
  const response = await fetch('https://proxy.cors.dev/' + snapshot, { credentials: 'omit' });
  if (!response.ok) {
    throw new Error(`Snapshot request failed: ${response.headers.get('X-Cors-Error') ?? response.status}`);
  }
  return new DOMParser().parseFromString(await response.text(), 'text/html');
}

const output = document.querySelector('#archived');
archivedPage('20200101000000', 'https://example.com/')
  .then((archived) => {
    const links = [...archived.querySelectorAll('a[href]')].map((link) => link.getAttribute('href'));
    output.textContent = `${archived.title}: ${links.length} links, first ${links[0] ?? 'none'}`;
  })
  .catch((error) => {
    output.textContent = error.message;
  });

Even a full timestamp redirects: 20200101000000id_ landed on 20191231234501, and a short 2020id_ on a capture from January 2021. The proxy follows up to 4 redirects but does not say which capture it reached, so take exact timestamps from CDX. Wikipedia's main page (74 KB) and the BBC home page (229 KB) worked; CNN's 1.1 MB page returned 502 response_too_large, and Pro raises the limit to 6 MiB. im_ returns image bytes, which the free tier refuses; an <img> tag needs no CORS, so point it at the capture URL instead.

Save Page Now belongs on a server

Save Page Now 2 is a POST to https://web.archive.org/save with the header Authorization: LOW accesskey:secret, using the S3-style keys from your archive.org account, followed by polling /save/status/<job_id>. The secret must stay on your server or Worker: anything in browser code is readable in DevTools, and cors.dev never stores or adds upstream keys. Run captures from your backend and read the results from the browser.

Rate limits and errors

  • Internet Archive limits: bursts get 429. Internet Archive staff have described the CDX limit as about 60 requests per minute, with a one-hour firewall block, doubling on repeat, for clients that ignore 429s. Our test machine was refused connections for a few minutes after a burst.
  • A throttled lookup looks like a CORS error: archive.org's 429 page has no Access-Control-Allow-Origin header, so fetch() rejects with a CORS error instead of returning 429. Through the proxy, a 429 arrives with CORS headers and your code can read it.
  • Free tier rules: GET and HEAD only, up to 1 MiB, no daily or monthly quota, 600 requests per minute per client IP, 10 seconds for the archive to answer, and only Accept, If-None-Match and If-Modified-Since are forwarded. Pro adds POST, custom headers and 6 MiB responses for $5 per month.

Good questions.

Is there a Wayback Machine API?

Yes. The availability API (archive.org/wayback/available) finds the closest capture of a URL, the CDX API (web.archive.org/cdx/search/cdx) lists every capture, and capture URLs with the id_ flag return archived HTML. All are free, and reading needs no key.

Does the Wayback Machine API support CORS?

Partly. archive.org/wayback/available, advancedsearch.php and /metadata send Access-Control-Allow-Origin: *, so call them directly. The CDX API and captures on web.archive.org send no CORS header; prefix those with https://proxy.cors.dev/.

How do I get all snapshots of a URL?

Query the CDX API: https://web.archive.org/cdx/search/cdx?url=example.com&output=json&limit=100. The first row is the column names. Add collapse=digest to skip unchanged captures, or collapse=timestamp:4 for one per year.

How do I get the original page without the Wayback toolbar?

Add id_ after the timestamp: https://web.archive.org/web/20200101000000id_/https://example.com/ returns the HTML as captured, without replay scripts or rewritten links. The archive redirects to the nearest capture, so take exact timestamps from CDX.

Does the Internet Archive API need an API key?

Not for reading. Availability, CDX, captures, search and metadata are keyless. Uploads and Save Page Now need the S3-style key and secret from your archive.org account, and the secret belongs on a server.

Why does the Wayback Machine return 429 or refuse connections?

It limits requests per IP address. Bursts get 429, and a client that keeps going is blocked at the firewall, which shows up as connection refused. Stay near one CDX call per second, cache answers and pause for a minute after a 429.

Can I save a page to the Wayback Machine from JavaScript?

Not safely from the browser. Save Page Now 2 needs a POST with your archive.org key and secret, and a secret in front-end code is public. Call web.archive.org/save from your server, then read the capture from the browser.