Updated Oct 2026
App Store reviews API in Python: JWT, pagination and replies
The App Store Connect API returns every written review of your apps and lets you post, replace and delete developer responses. The price is the authentication: every request needs a short-lived JWT signed with ES256 from a team API key. This guide does the whole thing in Python with PyJWT and requests, then shows the same two calls through Prisma. Creating the key, with the right role, is covered step by step in App Store Connect API key for reviews.
pip install "PyJWT[crypto]" requestsYou need three values from App Store Connect → Users and Access → Integrations → App Store Connect API: the Issuer ID, the Key ID and the downloaded .p8 file (downloadable once). The key must have the Customer Support, App Manager or Admin role; other roles get 403 on reviews.
1. Sign the token
Apple accepts tokens valid for at most 20 minutes; sign a fresh one when it’s close to expiring.
import time, jwt, os
ISSUER_ID = os.environ["ASC_ISSUER_ID"]
KEY_ID = os.environ["ASC_KEY_ID"]
PRIVATE_KEY = open("AuthKey_XXXXXXXXXX.p8").read()
def token():
now = int(time.time())
return jwt.encode(
{"iss": ISSUER_ID, "iat": now, "exp": now + 15 * 60, "aud": "appstoreconnect-v1"},
PRIVATE_KEY, algorithm="ES256", headers={"kid": KEY_ID, "typ": "JWT"},
)2. Find the app ID
import requests
API = "https://api.appstoreconnect.apple.com/v1"
def h(): return {"Authorization": f"Bearer {token()}"}
apps = requests.get(f"{API}/apps", headers=h(), params={"limit": 200}, timeout=30).json()["data"]
for a in apps:
print(a["id"], a["attributes"]["name"], a["attributes"]["bundleId"])
APP_ID = apps[0]["id"]3. List every review, page by page
def all_reviews(app_id):
url = f"{API}/apps/{app_id}/customerReviews"
params = {"limit": 200, "sort": "-createdDate", "include": "response"}
while url:
r = requests.get(url, headers=h(), params=params, timeout=30)
r.raise_for_status()
data = r.json()
yield from data["data"]
url = data.get("links", {}).get("next") # absolute URL; params are already in it
params = None
for rev in all_reviews(APP_ID):
a = rev["attributes"]
print(a["rating"], a["territory"], a["createdDate"], a.get("title"), (a.get("body") or "")[:80])Each review has rating (1 to 5), title, body, reviewerNickname, createdDate and territory (the storefront, like USA or ESP). Filter by territory with filter[territory]=USA, or by rating with filter[rating]=1. The response relationship is the developer reply, if any; fetch it directly with GET /v1/customerReviews/{id}/response.
Two things the endpoint doesn’t give you: ratings without a written review (they exist in App Store Connect, not in this API), and webhooks. Poll on a schedule and store what you read.
4. Reply, replace or delete a developer response
def reply(review_id, text):
body = {"data": {"type": "customerReviewResponses",
"attributes": {"responseBody": text},
"relationships": {"review": {"data": {"type": "customerReviews", "id": review_id}}}}}
r = requests.post(f"{API}/customerReviewResponses", headers=h(), json=body, timeout=30)
r.raise_for_status()
return r.json()["data"]["id"]
response_id = reply(rev["id"], "Thanks for the report, the crash on export is fixed in 4.2.1, out this week.")
# posting again for the same review replaces the response; to remove it:
requests.delete(f"{API}/customerReviewResponses/{response_id}", headers=h(), timeout=30).raise_for_status()Responses are limited to 5,970 characters, go through Apple’s moderation and show on the store a few hours later; the user who wrote the review is notified. A response posted by API is identical to one posted in App Store Connect.
5. The same job through Prisma
Prisma holds your App Store Connect key (encrypted, Customer Support role, checked against Apple before it is stored), signs the JWTs, polls, keeps the history and pushes a webhook per new review. Your code uses one key for the App Store, Google Play and 50+ other platforms.
import os, requests
H = {"Authorization": f"Bearer {os.environ['PRISMA_API_KEY']}"}
r = requests.get("https://api.prismareviews.com/v1/reviews", headers=H,
params={"platform": "app_store", "has_reply": "false", "limit": 100}, timeout=30)
for rev in r.json()["data"]:
print(rev["rating"], rev["language"], rev["title"], rev["text"])
requests.post(f"https://api.prismareviews.com/v1/reviews/{rev['id']}/reply", headers=H,
json={"text": "Thanks for the report, the crash on export is fixed in 4.2.1, out this week."}, timeout=30).raise_for_status()The reply is queued, published through Apple’s API and confirmed by a reply.published webhook; if Apple refuses it you get reply.failed with Apple’s reason. Replies on the App Store work in Prisma today (it is one of the platforms where publishing by API is live). Connecting the key: Connections; fields and limits: App Store on Platforms.
FAQ
Which App Store Connect role can read customer reviews?
Customer Support is the least-privileged role that can read reviews and post responses; Admin and App Manager also can. Developer, Marketing and Finance keys get 403 on customerReviews.
Does the App Store Connect API return ratings without text?
No. The customerReviews resource only returns written reviews. Star ratings without a review are not exposed by the API; the aggregate shown in App Store Connect comes from a different source.
Can I edit or delete a developer response by API?
Yes. Creating a customerReviewResponse for a review that already has one replaces it, and DELETE /v1/customerReviewResponses/{id} removes it.
App Store and Google Play reviews, one key, replies included.
2 locations free. Test keys return sample reviews, so the code above runs today.