How to export App Store reviews (free, no API key)
If you want to read App Store reviews in a spreadsheet, yours or a competitor's, Apple doesn't make it obvious. There are three real routes, and which one you want depends on whose app it is and how many reviews you need.
| Route | Whose app | How many | Needs |
|---|---|---|---|
| A. App Store Connect | Yours | All of them, to read | Your developer login |
| B. Public RSS feed | Any app | Newest 500 per country at most | Nothing |
| C. App Store Connect API | Yours | All of them, as data | An API key and a signed token |
Route A: App Store Connect (your own app)
Apple's help page gives the steps:
- In Apps, open your app.
- In the sidebar, click Ratings and Reviews.
- At the top right, pick the platform.
- At the top left, pick a country or region. Reviews are per storefront, so "United States" and "India" are separate lists.
- Filter by version, by rating, or by reviews that were edited or answered.
This is where most people reply to reviews, and it has your full history. What it doesn't have is an export: the help page (checked 7 October 2026) describes no download button and points to the App Store Connect API instead. Copying by hand is fine for 20 reviews and miserable for 500. For anything you want to count or tag, use B or C.
Route B: the public RSS feed (any app, no key)
Apple still serves an old customer-reviews feed for every app, in every storefront. Nobody needs to sign in. The URL pattern:
https://itunes.apple.com/{country}/rss/customerreviews/page={1-10}/id={appId}/sortby=mostrecent/json
{appId}is the number in the app's App Store link:apps.apple.com/us/app/todoist-to-do-list-calendar/id572688855has the id572688855.{country}is the two-letter storefront code:us,gb,in,de.- Swap
jsonforxmlif you prefer.
What I measured on 7 October 2026:
- Each page has 50 reviews.
- Pages 1 to 10 work. Page 11 returns an error (HTTP 400). So the ceiling is 500 reviews per country.
- For Todoist in the US store, all 10 pages came back: 500 reviews from 1 January 2025 to 3 October 2026. A small app reaches much further back; a huge one may cover only a few weeks.
- Each review has the date, star rating, app version, title, text, the reviewer's nickname and vote counts.
- Some pages come back empty. For Things 3 in the US store, pages 4 and 10 were empty on every try while the pages around them were full, so I got 400 reviews, not 500, with a gap in the middle. The day before, Todoist's page 9 did the same. Don't stop at the first empty page, and don't assume you got everything.
Here's a script that walks all ten pages and writes a CSV. It uses only the Python standard library.
import csv, json, sys, time, urllib.request
app_id, country = sys.argv[1], (sys.argv[2] if len(sys.argv) > 2 else "us")
rows = []
for page in range(1, 11):
url = f"https://itunes.apple.com/{country}/rss/customerreviews/page={page}/id={app_id}/sortby=mostrecent/json"
for attempt in range(3): # a page can come back empty while later pages are full: retry, never stop early
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
entries = json.load(urllib.request.urlopen(req, timeout=30))["feed"].get("entry", [])
if isinstance(entries, dict): entries = [entries]
if entries: break
time.sleep(3)
for e in entries:
rows.append([e["updated"]["label"][:10], e["im:rating"]["label"], e["im:version"]["label"],
e["title"]["label"], e["content"]["label"]])
time.sleep(1)
with open(f"reviews-{app_id}-{country}.csv", "w", newline="", encoding="utf-8") as f:
w = csv.writer(f)
w.writerow(["date", "rating", "version", "title", "review"])
w.writerows(rows)
print(f"{len(rows)} reviews, {rows[-1][0]} to {rows[0][0]}" if rows else "no reviews in this storefront")
It's also on GitHub as export_reviews.py in sumantop/app-store-tools (MIT). Run it with the app id and a country:
$ python3 export_reviews.py 572688855 us
500 reviews, 2025-01-01 to 2026-10-03
That run took 17 seconds, most of it the pauses. A few notes:
- I leave out the reviewer's nickname on purpose. You rarely need it, and the file is safer to share without it.
- The one-second pause between pages is courtesy. Apple doesn't publish a rate limit for this feed, and I'd rather not find it.
- A single review can come back as an object instead of a list, which is why there's an
isinstancecheck.
For a longer version that takes a full App Store link, retries network errors and explains each choice, see Scrape App Store reviews with Python.
The no-code option. If you don't want to run Python, the free App Store review exporter on this site does this step in your browser, with no login: paste an App Store link, pick a country, download a CSV. I built it, so here are the trade-offs plainly: it gets up to the 200 newest reviews per country (not 500) and has no app version column. If you need 500 or the version, use the script.
Route C: the App Store Connect API (your app, every review)
If it's your app and you want everything, the API is the complete route. It's more setup, and you only do it once.
- In App Store Connect, go to Users and Access > Integrations > App Store Connect API and create a key. You get a key id, an issuer id and a
.p8file you can download only once. Keep it somewhere safe. - Sign a short-lived token (ES256, at most 20 minutes) with that key.
- Call the customer reviews endpoint for your app id.
# pip install pyjwt cryptography
import json, time, urllib.request, jwt
KEY_ID, ISSUER, APP_ID = "ABC123DEFG", "your-issuer-uuid", "1234567890"
token = jwt.encode({"iss": ISSUER, "iat": int(time.time()), "exp": int(time.time()) + 1200, "aud": "appstoreconnect-v1"},
open(f"AuthKey_{KEY_ID}.p8").read(), algorithm="ES256", headers={"kid": KEY_ID})
url = f"https://api.appstoreconnect.apple.com/v1/apps/{APP_ID}/customerReviews?limit=200&sort=-createdDate"
reviews = []
while url:
req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"})
page = json.load(urllib.request.urlopen(req))
reviews += [r["attributes"] for r in page["data"]]
url = page["links"].get("next")
print(len(reviews))
Each review has the rating, title, body, reviewer nickname, created date and territory. You can filter with filter[territory]=USA or filter[rating]=1. The token expires after 20 minutes, so a very large app needs a new one partway through.
Two things to know: it only works for apps in your own team, and the key needs a role with access to customer reviews. It's also the only route that gives you the full history in a form you can script, and the API can post replies too.
Caveats that apply to all three
- Storefronts are separate. A US export says nothing about your reviews in Germany or India. If you sell in several countries, run the export per country and add a country column.
- The public routes are recency-biased. 500 reviews is a lot for a small app and a few weeks for a big one. Anything you count from Route B is a snapshot, not your app's whole story.
- Not every rating has a review. Most people tap stars and leave. Written reviews skew toward the very happy and the very annoyed.
- Reviews go missing. Apple removes some reviews, and the public feed sometimes skips whole pages. Only Route C is complete.
What to do with the CSV
Once you have the file, the useful part takes about half an hour in a spreadsheet.
- Split by rating. Filter to 1 and 2 stars: that's your bug list. Filter to 4 and 5: that's how happy users describe your app, in their words.
- Add a theme column. In Google Sheets,
=IF(REGEXMATCH(LOWER(E2), "subscri|price|paywall|charged"), "price", "")tags every review that talks about money. Make one column per theme: price, crash, sync, a feature name, "tried other apps". - Count before you believe anything.
=COUNTIF(F:F, "price")next to the total row count. Ten matches out of 300 is a pattern. One vivid review isn't. - Check by version. If you used the RSS route, group the 1-star reviews by the version column. A spike right after one release tells you more than any overall average.
I did this across 5,920 reviews of 100 apps and wrote up what users actually praise and complain about, category by category.
How this post was made
I drafted this with AI help and checked it the same day: both scripts and the page limits were re-run live on 7 October 2026, and Route A was checked against Apple's help page. The API snippet follows Apple's App Store Connect API documentation and wasn't run for this post.