> ## Documentation Index
> Fetch the complete documentation index at: https://docs.appeeky.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Customer Reviews

> Search reviews with rich filters, get summary stats, and post or delete developer responses — all backed by a synced copy of your App Store Connect data

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.

<Note>
  **Requires connected ASC account.** Connect your App Store Connect API key and Vendor Number in [appeeky.com → Settings → Integrations](https://appeeky.com), or pass `X-ASC-*` headers per request. Reviews are synced daily and right after you connect.
</Note>

<Info>
  **Pro feature** — requires Indie plan or higher.
</Info>

***

## 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

```
GET /v1/connect/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). |

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  # 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"
  ```

  ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const params = new URLSearchParams({
    appId: "6759740679",
    rating: "1",
    hasResponse: "false",
    q: "crash",
    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();
  ```
</CodeGroup>

**Response (200 OK):**

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "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.                           |

### Pagination

There is no `next` cursor in the response — pagination uses the `before` parameter. To get the next page:

```javascript theme={"theme":{"light":"github-light","dark":"github-dark"}}
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. |

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl "https://api.appeeky.com/v1/connect/reviews/summary?appId=6759740679" \
    -H "X-API-Key: apk_your_key_here"
  ```
</CodeGroup>

**Response (200 OK):**

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "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

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "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). |

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  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."}'
  ```

  ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const res = await fetch(
    `https://api.appeeky.com/v1/connect/reviews/${reviewId}/response`,
    {
      method: "POST",
      headers: {
        "X-API-Key": process.env.APEEKY_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        responseBody: "Thanks for the report — fixed in 2.4.2.",
      }),
    }
  );
  ```
</CodeGroup>

**Response (200 OK):**

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "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"
    }
  }
}
```

<Tip>
  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.
</Tip>

***

## 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`.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X DELETE "https://api.appeeky.com/v1/connect/reviews/00000000-0000-0000-0000-000000000abc/response" \
    -H "X-API-Key: apk_your_key_here"
  ```
</CodeGroup>

**Response (200 OK):**

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "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.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST "https://api.appeeky.com/v1/connect/reviews/00000000-0000-0000-0000-000000000abc/refresh" \
    -H "X-API-Key: apk_your_key_here"
  ```
</CodeGroup>

**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. |

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST "https://api.appeeky.com/v1/connect/reviews/sync?mode=incremental" \
    -H "X-API-Key: apk_your_key_here"
  ```
</CodeGroup>

**Response (200 OK):**

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "data": {
    "syncRunId": "run_abc123",
    "mode": "incremental"
  }
}
```

<Note>
  Sync requests are deduplicated — clicking sync twice within 30 minutes returns the same `syncRunId`.
</Note>

***

## Common Workflow: Triage Low-Rated Reviews

```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
// 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`);
```

***

## MCP Tools

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.
