Manage your App Store customer reviews end-to-end from a single API. Reviews are kept in sync with App Store Connect on a daily cadence and on demand, so you can search and filter them by rating, country, response state, or full-text query without paginating Apple’s API on every request.
Requires connected ASC account. Connect your App Store Connect API key and Vendor Number in appeeky.com → Settings → Integrations, or pass X-ASC-* headers per request. Reviews are synced daily and right after you connect.
Pro feature — requires Indie plan or higher.
Overview
| Endpoint | Description |
|---|
GET /v1/connect/reviews | Search reviews with filters (rating, territory, response state, full-text) |
GET /v1/connect/reviews/summary | Aggregated stats: avg rating, distribution, response rate, by-territory |
POST /v1/connect/reviews/{reviewId}/response | Create or update your reply to a review |
DELETE /v1/connect/reviews/{reviewId}/response | Delete your reply to a review |
POST /v1/connect/reviews/{reviewId}/refresh | Force-refresh one review from Apple (e.g. right after replying) |
POST /v1/connect/reviews/sync | Trigger an immediate background sync of all reviews |
How freshness works
- Reviews refresh automatically every day.
- Posting or deleting a reply writes to Apple synchronously and updates your local copy in the same request, so the next list call reflects the change immediately.
- Use
POST /v1/connect/reviews/{reviewId}/refresh to pull the latest state of a single review from Apple on demand (useful right after replying to confirm the response was published).
- Use
POST /v1/connect/reviews/sync to trigger a full background refresh of every review.
Search Reviews
Returns reviews sorted by newest first. All filter parameters are optional and combinable — for example, “all unanswered 1-2 star reviews from the US that mention ‘crash’” is a single request.
Query parameters
| Name | Type | Default | Description |
|---|
appId | string | — | Filter by App Store Connect app ID. Omit to search across all your connected apps. |
rating | number | — | Filter by exact star rating (1–5). |
territory | string | — | ISO 3-letter country code (e.g. USA, GBR, DEU). |
hasResponse | boolean | — | true for answered reviews only, false for unanswered only. Omit for both. |
q | string | — | Case-insensitive substring match in review title or body. |
limit | number | 50 | Max results (1–200). |
before | string | — | ISO timestamp cursor — returns reviews created strictly before this date (use the oldest review_created_at from the previous page). |
# Triage: unanswered 1-star reviews mentioning "crash"
curl "https://api.appeeky.com/v1/connect/reviews?rating=1&hasResponse=false&q=crash" \
-H "X-API-Key: apk_your_key_here"
Response (200 OK):
{
"data": {
"reviews": [
{
"id": "00000000-0000-0000-0000-000000000abc",
"app_apple_id": "6759740679",
"rating": 1,
"title": "Crashes on launch",
"body": "Latest update crashes immediately when I open the app...",
"reviewer_nickname": "MarcusJ",
"territory": "USA",
"app_version": "2.4.1",
"review_created_at": "2026-04-22T14:18:00Z",
"response_id": null,
"response_body": null,
"response_state": null,
"response_last_modified": null
}
],
"count": 1
}
}
| Field | Description |
|---|
id | Apple review ID — pass this to the response and refresh endpoints. |
rating | 1–5 stars. |
title / body | Review text. |
reviewer_nickname | Anonymous nickname chosen by the reviewer. |
territory | ISO 3-letter country code. |
app_version | App version the user reviewed. |
review_created_at | ISO timestamp the user submitted the review. |
response_id | null if you have not replied yet. |
response_body | Your last reply text (or null). |
response_state | PUBLISHED, PENDING_PUBLISH, or null. |
response_last_modified | ISO timestamp of your last reply update. |
There is no next cursor in the response — pagination uses the before parameter. To get the next page:
const lastReviewDate = data.reviews[data.reviews.length - 1].review_created_at;
const next = await fetch(
`https://api.appeeky.com/v1/connect/reviews?limit=50&before=${encodeURIComponent(lastReviewDate)}`,
{ headers: { "X-API-Key": process.env.APEEKY_KEY } }
);
Reviews Summary
GET /v1/connect/reviews/summary
Aggregated statistics for an app (or all your apps). Useful for dashboards and quick health snapshots.
Query parameters
| Name | Type | Default | Description |
|---|
appId | string | — | Filter by app. Omit to roll up across all your apps. |
from | string | — | ISO date — only consider reviews created on/after this. |
to | string | — | ISO date — only consider reviews created on/before this. |
curl "https://api.appeeky.com/v1/connect/reviews/summary?appId=6759740679" \
-H "X-API-Key: apk_your_key_here"
Response (200 OK):
{
"data": {
"appId": "6759740679",
"from": null,
"to": null,
"total": 482,
"avg_rating": 4.32,
"unanswered": 67,
"response_rate": 0.8610,
"distribution": {
"1": 22,
"2": 14,
"3": 38,
"4": 110,
"5": 298
},
"territories": [
{ "territory": "USA", "count": 219, "avg_rating": 4.41 },
{ "territory": "GBR", "count": 58, "avg_rating": 4.22 },
{ "territory": "DEU", "count": 41, "avg_rating": 4.05 }
]
}
}
| Field | Description |
|---|
total | Total review count in the window. |
avg_rating | Average star rating (1–5). |
unanswered | Reviews with no developer response. |
response_rate | (total − unanswered) / total, rounded to 4 decimals. |
distribution | Count per star rating. |
territories | Up to 50 territories sorted by review volume, each with its own avg rating. |
Respond to a Review
POST /v1/connect/reviews/{reviewId}/response
Create or update your developer response. Apple does not allow editing a response in place — under the hood, this endpoint deletes the existing response and creates a new one when needed. You always make a single API call.
Path parameters
| Name | Description |
|---|
reviewId | Apple review ID (id from the search response). |
Request body
{
"responseBody": "Thanks for the report — this is fixed in 2.4.2 (rolling out today). Please email support@example.com if you still see the issue."
}
| Field | Type | Required | Description |
|---|
responseBody | string | Yes | Your reply text. Non-empty, max 5,970 characters (Apple’s hard limit). |
curl -X POST "https://api.appeeky.com/v1/connect/reviews/00000000-0000-0000-0000-000000000abc/response" \
-H "X-API-Key: apk_your_key_here" \
-H "Content-Type: application/json" \
-d '{"responseBody":"Thanks for the report — fixed in 2.4.2."}'
Response (200 OK):
{
"data": {
"reviewId": "00000000-0000-0000-0000-000000000abc",
"response": {
"response_id": "11111111-1111-1111-1111-111111111def",
"response_body": "Thanks for the report — fixed in 2.4.2.",
"response_state": "PENDING_PUBLISH",
"response_last_modified": "2026-04-25T22:01:09Z"
}
}
}
Apple typically returns PENDING_PUBLISH initially and transitions to PUBLISHED once moderation completes (usually within minutes). Call the refresh endpoint below to see the published state without waiting for the next daily sync.
Delete a Response
DELETE /v1/connect/reviews/{reviewId}/response
Removes your developer response. The review itself remains; only your reply is deleted. Idempotent — calling it on a review with no response returns 200 with response: null.
curl -X DELETE "https://api.appeeky.com/v1/connect/reviews/00000000-0000-0000-0000-000000000abc/response" \
-H "X-API-Key: apk_your_key_here"
Response (200 OK):
{
"data": {
"reviewId": "00000000-0000-0000-0000-000000000abc",
"response": null
}
}
Refresh One Review
POST /v1/connect/reviews/{reviewId}/refresh
Pulls the latest state of a single review directly from Apple. Use this right after posting a response to confirm the PUBLISHED transition, or any time you suspect a review is stale.
curl -X POST "https://api.appeeky.com/v1/connect/reviews/00000000-0000-0000-0000-000000000abc/refresh" \
-H "X-API-Key: apk_your_key_here"
Response (200 OK): the same review shape returned by GET /v1/connect/reviews.
Trigger Background Sync
POST /v1/connect/reviews/sync
Enqueues a background job that pulls every new review since the last sync (or all reviews if mode=full). Useful when you want fresh data on demand without waiting for the daily refresh. Returns a syncRunId you can use to track the job.
Query parameters
| Name | Type | Default | Description |
|---|
mode | string | incremental | incremental for new reviews only, full for a complete backfill. |
curl -X POST "https://api.appeeky.com/v1/connect/reviews/sync?mode=incremental" \
-H "X-API-Key: apk_your_key_here"
Response (200 OK):
{
"data": {
"syncRunId": "run_abc123",
"mode": "incremental"
}
}
Sync requests are deduplicated — clicking sync twice within 30 minutes returns the same syncRunId.
Common Workflow: Triage Low-Rated Reviews
// 1. Find unanswered low-rated reviews
const params = new URLSearchParams({
rating: "1",
hasResponse: "false",
limit: "50",
});
const res = await fetch(
`https://api.appeeky.com/v1/connect/reviews?${params}`,
{ headers: { "X-API-Key": process.env.APEEKY_KEY } }
);
const { data } = await res.json();
// 2. Reply to each one
for (const review of data.reviews) {
await fetch(
`https://api.appeeky.com/v1/connect/reviews/${review.id}/response`,
{
method: "POST",
headers: {
"X-API-Key": process.env.APEEKY_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
responseBody:
"We're sorry to hear that. Please email support@example.com so we can help.",
}),
}
);
}
// 3. Confirm by checking the summary
const summary = await fetch(
"https://api.appeeky.com/v1/connect/reviews/summary",
{ headers: { "X-API-Key": process.env.APEEKY_KEY } }
).then((r) => r.json());
console.log(`${summary.data.unanswered} unanswered reviews remain`);
These endpoints are also exposed as MCP tools so AI assistants can triage reviews on your behalf:
| Tool | Description |
|---|
asc_search_reviews | Search reviews with filters (rating, territory, hasResponse, full-text) |
asc_reviews_summary | Aggregated stats and response rate |
asc_respond_to_review | Post or update a developer response |
asc_delete_review_response | Delete a developer response |
asc_refresh_review | Refresh a single review from Apple |
Example prompt:
Find every unanswered 1- or 2-star review for app 6759740679 from the last
two weeks that mentions "crash" or "bug", and reply with an empathetic
message that points to support@example.com.
Error Codes
| Status | Code | When |
|---|
| 400 | INVALID_REVIEW_ID | Missing reviewId path parameter |
| 400 | INVALID_RESPONSE_BODY | Empty responseBody |
| 400 | RESPONSE_BODY_TOO_LONG | Reply exceeds Apple’s 5,970-character limit |
| 400 | ASC_CREDENTIALS_REQUIRED | No App Store Connect credentials connected for your account |
| 401 | INVALID_API_KEY | Invalid or inactive API key |
| 403 | PRO_FEATURE | Free plan — upgrade to Indie or higher |
| 404 | REVIEW_NOT_FOUND | Review does not exist or is not owned by your account |
| 500 | REVIEWS_FETCH_FAILED | Database error |
| 500 | REVIEWS_SYNC_ENQUEUE_FAILED | Could not enqueue sync job |
Legacy Endpoints
The original live-passthrough endpoints remain available for backwards compatibility:
| Method | Path | Notes |
|---|
GET | /v1/connect/apps/{appId}/customer-reviews | Live paginated read from Apple. No filters beyond pagination. |
POST | /v1/connect/customer-reviews/response | Body: { reviewId, responseBody }. Does not handle response edits. |
For all new integrations, prefer the endpoints above — they are faster, support filtering and aggregation, transparently handle response edits, and keep your local view in sync with Apple.