Legacy Store

API

Everything on this site is available as a totally open, read-only JSON API. No keys, no auth, CORS is open. It stays that way because people are decent about it, so please cache responses and keep request rates reasonable. The machine-readable contract is the OpenAPI spec; the API also describes itself at /api/v1.

Getting started

Base URL: https://legacystore.app/api/v1

  • No authentication.
  • All ids are JSON strings. Timestamps are ISO 8601 UTC.
  • Lists are {data, total, next_url}. Follow next_url for the next page.
  • Cursors are opaque — never construct one.
  • An app is addressed by App Store ID (canonical); a bundle_id or internal id 301-redirects, so pass -L to curl.
  • Errors are {error: {code, message}} with a real HTTP status.
  • Please cache. Responses carry long cache headers; this is a preservation project on shared infrastructure.

Endpoints

EndpointReturns
GET /api/v1Self-describing index
GET /api/v1/appsSearch / list the catalog
GET /api/v1/apps/idsEvery public app key, in one response
GET /api/v1/apps/{key}One app with its full version/copy tree
GET /api/v1/apps/{key}/versionsThe version/copy tree alone
GET /api/v1/apps/{key}/chartsThe app's chart trajectory
GET /api/v1/apps/{key}/ratingsRating/price capture history
GET /api/v1/apps/{key}/reviewsArchived customer reviews
GET /api/v1/apps/{key}/screenshotsArchived screenshot sets
GET /api/v1/copies/{ipa_id}One archived copy with binary analysis
GET /api/v1/chartsOne archived chart snapshot, ranked
GET /api/v1/charts/typesChart type slugs
GET /api/v1/genresGenres with app counts
GET /api/v1/collectionsEditorial collections
GET /api/v1/collections/{slug}One collection with its members
GET /api/v1/statsArchive-wide statistics
GET|POST /api/v1/coverageWhich of your IPAs the archive holds

Find an app

Search the catalog by keyword or bundle id.

GET /api/v1/apps

Parameters (none required):

  • q — search app names by keyword. If it contains a dot (com.rovio), it matches against bundle ids instead
  • genre — genre id from /api/v1/genres
  • developer — scope to one developer's apps (the developer_artist_id from any app)
  • sortversions (default) | relevance (best match for q, typo-tolerant) | first_date | newest | name
  • limit — 1–200, default 50
curl 'https://legacystore.app/api/v1/apps?q=doodle&limit=2'
{
  "data": [
    {
      "app_store_id": "307727765",
      "bundle_id": "com.yourcompany.DoodleJump",
      "name": "Doodle Jump - BE WARNED: Insanely Addictive!",
      "developer": "Lima Sky",
      "genre_id": "6014",
      "version_count": 85,
      "icon_url": "https://legacystore.app/icon/3836…",
      "url": "https://legacystore.app/api/v1/apps/307727765",
      "web_url": "https://legacystore.app/app/307727765"
    },
    …
  ],
  "total": 188,
  "next_url": "https://legacystore.app/api/v1/apps?q=doodle&limit=2&cursor=JTVCMiU1RA"
}

Versions and download links

One app with every version and archived copy. install_status is the will-it-run answer: installable or encrypted. download_url 302s to the .ipa on the Internet Archive; manifest_url is an itms-services manifest for over-the-air install on a real device.

GET /api/v1/apps/{key}

Parameters: none.

curl -L 'https://legacystore.app/api/v1/apps/307727765'
{
  "app_store_id": "307727765",
  "name": "Doodle Jump - BE WARNED: Insanely Addictive!",
  "original_release_date": "2009-03-27T02:01:56.000Z",
  "genre": "Games",
  "version_count": 85,
  "versions": [
    {
      "version": "3.20.18",
      "minimum_os_version": "15.0",
      "copies": [
        {
          "ipa_id": "99743",
          "size": 63692423,
          "sha1": "79d76b3585cc10cc3f6506dfdaaf120bf5fa76ba",
          "install_status": "installable",
          "architectures": [
            "arm64"
          ],
          "download_url": "https://legacystore.app/ipa/99743",
          "manifest_url": "https://legacystore.app/manifest/99743.plist"
        }
      ]
    },
    …
  ]
}

/api/v1/apps/{key}/versions returns the same tree alone. /api/v1/copies/{ipa_id} is one copy with the full binary analysis (Retina, Watch app, extensions…).

Walk a chart

One archived chart snapshot with its ranked positions. The archive is sparse, so you get the snapshot nearest your date — snapshot_date says which, and wayback_url is the exact capture it was rebuilt from.

GET /api/v1/charts

Parameters (none required):

  • type — slug from /api/v1/charts/types, default top-free
  • date — YYYY-MM-DD; nearest archived snapshot is returned
  • genre — genre id for genre charts; omit for the all-apps chart
  • deviceiphone (default) | ipad
curl 'https://legacystore.app/api/v1/charts?type=top-paid&date=2010-06-01'
{
  "chart_type": {
    "name": "Top Paid",
    "slug": "top-paid"
  },
  "device": "iphone",
  "snapshot_date": "2010-06-01",
  "wayback_url": "https://web.archive.org/web/20100601055221/http://www.apple.com/itunes/charts/paid-apps/",
  "available_dates": [
    "2008-12-15",
    "2008-12-16",
    …
  ],
  "positions": [
    {
      "position": 1,
      "app_store_id": "343200656",
      "name": "Angry Birds",
      "app": {
        "url": "https://legacystore.app/api/v1/apps/343200656",
        …
      }
    },
    {
      "position": 2,
      "app_store_id": "307727765",
      "name": "Doodle Jump - BE WARNED: Insanely Addictive!",
      …
    },
    …
  ]
}

One app's whole trajectory: /api/v1/apps/{key}/charts.

Reviews

Archived customer reviews, oldest first.

GET /api/v1/apps/{key}/reviews

Parameters (none required): limit — 1–200, default 50.

curl 'https://legacystore.app/api/v1/apps/307727765/reviews?limit=1'
{
  "data": [
    {
      "review_id": "10357181791521232",
      "title": "Favorite",
      "body": "Game. Still!",
      "stars": 5,
      "author": "Mitch/Replay",
      "reviewed_at": "2025-10-29T00:00:00.000Z",
      …
    }
  ],
  "total": 26,
  "next_url": "…cursor=JTVCMSU1RA"
}

Rating history

Raw archival captures of an app's rating count, average, and price over time, oldest first. Captures can mix storefronts — smooth client-side if you need a monotonic series.

GET /api/v1/apps/{key}/ratings

Parameters: none.

curl 'https://legacystore.app/api/v1/apps/307727765/ratings'
{
  "data": [
    {
      "captured_at": "2010-02-07T13:00:48.000Z",
      "rating_count": 23631,
      "listed_version": "1.13.4",
      "price_amount": 0.99,
      …
    },
    {
      "captured_at": "2011-01-06T23:20:53.000Z",
      "rating_count": 174041,
      …
    },
    …
  ]
}

Screenshots

One set per distinct capture, content-deduped, newest first.

GET /api/v1/apps/{key}/screenshots

Parameters: none.

curl 'https://legacystore.app/api/v1/apps/307727765/screenshots'
{
  "data": [
    {
      "captured_at": "2013-03-03T03:54:18.000Z",
      "version": "3.4",
      "shots": [
        {
          "url": "https://legacystore.app/screen/7dc234f5536435a6…",
          "width": 640,
          "height": 960
        },
        …
      ]
    },
    …
  ]
}

Collections

Editorial collections; each member keeps its curated order and label.

GET /api/v1/collections

Parameters: none. One collection: /api/v1/collections/{slug}.

curl 'https://legacystore.app/api/v1/collections'
{
  "data": [
    {
      "slug": "featured",
      "title": "Featured",
      "app_count": 26,
      "url": "https://legacystore.app/api/v1/collections/featured",
      …
    },
    {
      "slug": "ada-2008",
      "title": "Apple Design Awards 2008",
      "series": "Apple Design Award Winners",
      "app_count": 5,
      …
    },
    …
  ]
}

Bulk enumeration

Every public app key in one response; then GET /api/v1/apps/{key} per id — politely, please. Archive-wide numbers live at /api/v1/stats.

GET /api/v1/apps/ids

Parameters: none.

curl 'https://legacystore.app/api/v1/apps/ids'
{
  "data": [
    "100",
    "1000170341",
    "1000230545",
    "1000256889",
    …
  ],
  "total": …
}

Check coverage of your own IPAs

FairPlay only encrypts the executable — the plists in your own .ipa files stay readable — so you can ask which versions the archive already preserves without decrypting anything.

GET /api/v1/coverage/{bundle_id}/{version}
GET /api/v1/coverage?external_id={id}

external_id is softwareVersionExternalIdentifier from iTunesMetadata.plist — the exact App Store build, the most precise identifier to probe with. Batch: POST /api/v1/coverage with up to 500 probes, each carrying any of bundle_id, app_store_id, version, or external_id.

curl 'https://legacystore.app/api/v1/coverage/com.limasky.doodlejumpfree/1.0'
{
  "bundle_id": "com.limasky.doodlejumpfree",
  "version": "1.0",
  "external_id": "4033516",
  "app_store_id": "456355158",
  "copies": {
    "encrypted": 6,
    "installable": 8
  }
}

This API is the supported surface for the archive's data — anything else you find is undocumented and shifts without notice. The colophon tells the story of where all this data comes from.