App Store Connect API: A Practical Guide to Importing Reviews

January 13, 2026 12 min read Technical Guide
App Store Connect API integration for importing and managing app reviews

The App Store Connect API is one of those things that sounds simple and then isn't.

Apple's documentation is thorough in the way that a phone book is thorough — everything is technically in there, but good luck finding what you need. So here's the guide I wish existed when I started pulling reviews programmatically.

Loading and importing review data via App Store Connect API

What you need before you start

Three things. Apple makes this harder than it should be.

An API Key. Go to App Store Connect → Users and Access → Keys → App Store Connect API. Generate a key. You'll get a .p8 file (your private key), a Key ID, and an Issuer ID. Save all three. You can only download the .p8 file once — Apple isn't kidding about this. Lose it and you're generating a new key.

JWT signing. The API uses JWT tokens for authentication. No OAuth, no API keys in headers. You generate a short-lived JWT signed with your .p8 key, and include it as a Bearer token. The token expires after 20 minutes.

Here's the JWT structure:

import jwt
import time

ISSUER_ID = "your-issuer-id"
KEY_ID = "your-key-id"
PRIVATE_KEY = open("AuthKey_XXXXXXXXXX.p8").read()

payload = {
    "iss": ISSUER_ID,
    "iat": int(time.time()),
    "exp": int(time.time()) + 1200,  # 20 minutes
    "aud": "appstoreconnect-v1"
}

token = jwt.encode(
    payload,
    PRIVATE_KEY,
    algorithm="ES256",
    headers={"kid": KEY_ID}
)

Your app's resource ID. Every app in App Store Connect has an internal ID (not your bundle ID, not your Apple ID — a separate numeric ID). You'll need this to query reviews for a specific app.

Fetching reviews

Once you have auth and your app ID, pulling reviews is straightforward:

app_id = "123456789"
url = f"https://api.appstoreconnect.apple.com/v1/apps/{app_id}/customerReviews"

params = {
    "sort": "-createdDate",
    "limit": 50
}

response = requests.get(url, headers=headers, params=params)

Each review object gives you: attributes.rating (1 to 5), attributes.title, attributes.body, attributes.reviewerNickname, attributes.createdDate (ISO timestamp), and attributes.territory (country code).

Pagination (where it gets annoying)

Apple paginates everything. The links.next field in the response contains the URL for the next page. You keep following it until there's no next link.

all_reviews = []
url = f"https://api.appstoreconnect.apple.com/v1/apps/{app_id}/customerReviews"
params = {"sort": "-createdDate", "limit": 200}

while url:
    response = requests.get(url, headers=headers, params=params)
    data = response.json()
    all_reviews.extend(data["data"])
    url = data.get("links", {}).get("next")
    params = {}  # params embedded in next URL

print(f"Fetched {len(all_reviews)} reviews")

The gotcha nobody mentions: Apple's API can be slow. Really slow. A request that takes 200ms against most APIs can take 2-5 seconds against App Store Connect. If you're pulling thousands of reviews, budget for minutes, not seconds.

Rate limits

Apple doesn't publish hard rate limits, but based on experience you'll start getting 429 responses if you make more than about 30 requests per minute. Build in retry logic with exponential backoff.

import time

def fetch_with_retry(url, headers, max_retries=5):
    for attempt in range(max_retries):
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            return response.json()
        if response.status_code == 429:
            wait = 2 ** attempt
            time.sleep(wait)
            continue
        response.raise_for_status()
    raise Exception("Max retries exceeded")

Replying to reviews

Yes, you can reply to reviews via the API. This is the part that makes programmatic review management actually useful.

review_id = "some-review-id"
reply_url = (
    f"https://api.appstoreconnect.apple.com"
    f"/v1/customerReviews/{review_id}/response"
)

existing = requests.get(reply_url, headers=headers)

if existing.status_code == 200:
    response_id = existing.json()["data"]["id"]
    requests.patch(
        f"https://api.appstoreconnect.apple.com"
        f"/v1/customerReviewResponses/{response_id}",
        headers={**headers, "Content-Type": "application/json"},
        json={
            "data": {
                "type": "customerReviewResponses",
                "id": response_id,
                "attributes": {
                    "responseBody": "Fixed in v2.1. Thanks!"
                }
            }
        }
    )

The pieces Apple doesn't give you

A few things that aren't available via the API that you might expect. Historical ratings distribution — you can't pull "how many 5-star ratings did I have last month." Review edits — if a user edits their review, you'll see the updated version but there's no edit history. Review deletions — if Apple removes a review, it just disappears. Webhooks — there aren't any. You have to poll.

Putting it all together

If you're building this yourself, the architecture is simple: a cron job that runs every N hours, fetches new reviews since the last run, stores them in your database, and optionally sends you a notification for reviews matching certain criteria.

That's essentially what AppTriage does under the hood, plus a web UI for reading and triaging everything.

The App Store Connect API is powerful once you get past the JWT auth setup. The documentation is fine. But the practical experience of working with it — the slow responses, the pagination quirks, the missing webhooks — is something you only learn by building.


Don't want to build your own API integration? AppTriage has a built-in App Store review tracker that auto-imports your reviews hourly. Works with both App Store and Google Play in one inbox.