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}. Follownext_urlfor the next page. - Cursors are opaque — never construct one.
- An app is addressed by App Store ID (canonical); a
bundle_idor internal id 301-redirects, so pass-Lto 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
| Endpoint | Returns |
|---|---|
GET /api/v1 | Self-describing index |
GET /api/v1/apps | Search / list the catalog |
GET /api/v1/apps/ids | Every public app key, in one response |
GET /api/v1/apps/{key} | One app with its full version/copy tree |
GET /api/v1/apps/{key}/versions | The version/copy tree alone |
GET /api/v1/apps/{key}/charts | The app's chart trajectory |
GET /api/v1/apps/{key}/ratings | Rating/price capture history |
GET /api/v1/apps/{key}/reviews | Archived customer reviews |
GET /api/v1/apps/{key}/screenshots | Archived screenshot sets |
GET /api/v1/copies/{ipa_id} | One archived copy with binary analysis |
GET /api/v1/charts | One archived chart snapshot, ranked |
GET /api/v1/charts/types | Chart type slugs |
GET /api/v1/genres | Genres with app counts |
GET /api/v1/collections | Editorial collections |
GET /api/v1/collections/{slug} | One collection with its members |
GET /api/v1/stats | Archive-wide statistics |
GET|POST /api/v1/coverage | Which 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 insteadgenre— genre id from/api/v1/genresdeveloper— scope to one developer's apps (thedeveloper_artist_idfrom any app)sort—versions(default) |relevance(best match forq, typo-tolerant) |first_date|newest|namelimit— 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, defaulttop-freedate— YYYY-MM-DD; nearest archived snapshot is returnedgenre— genre id for genre charts; omit for the all-apps chartdevice—iphone(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.