> For the complete documentation index, see [llms.txt](https://docs.pullbay.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.pullbay.com/documentation/guides/what-is-review-monitoring/app-store-review-api-guide.md).

# App Store Review API Guide

When you're building or maintaining an app, understanding user feedback is critical. But manually checking App Store reviews doesn't scale. An app store review API lets you pull review data automatically, in structured format, so you can build tools, track trends, and respond to feedback at scale. This guide covers everything you need to know about Pullbay's App Store Review API and how to use it in your workflows.

***

## What Is an App Store Review API?

An app store review API is a programmatic interface that lets you retrieve user reviews, ratings, and metadata directly from app stores like Apple's App Store. Instead of logging in manually and scrolling through reviews, an API lets you:

* **Fetch reviews programmatically** using code
* **Retrieve structured data** — score, text, author, date, country, version, and more
* **Automate monitoring** with scheduled requests
* **Scale to hundreds of reviews** without manual effort
* **Integrate with your own tools** — dashboards, alerts, CRMs, databases

### Why You Need an App Store Review API

Manual review monitoring is slow and error-prone. An API solves three core problems:

1. **Speed** — get fresh review data in seconds, not hours
2. **Scale** — fetch up to 500 reviews automatically without clicking through pages
3. **Integration** — feed review data directly into your analytics, CRM, or alerting systems

Whether you're tracking competitor apps, monitoring your own reputation, or analysing user feedback for feature development, an API approach is faster and more reliable than manual tools.

***

## How Pullbay's App Store Review API Works

Pullbay's API retrieves reviews from Apple's App Store in three steps:

{% stepper %}
{% step %}

## You send a request

Specify the app you want (by numeric App Store ID or bundle ID) and any options (country, sort order, number of reviews).
{% endstep %}

{% step %}

## Pullbay fetches the data

Pullbay calls Apple's review feed, handles pagination, deduplication, and normalisation, and returns a clean response.
{% endstep %}

{% step %}

## You receive structured JSON

Reviews arrive as clean, normalised JSON with consistent field names, data types, and a standard response envelope.

You don't need Apple Developer credentials or App Store tokens — just your Pullbay API key and the app's ID.
{% endstep %}
{% endstepper %}

***

## Key Features

### Two Fetch Modes

The `/api/appstore/reviews` endpoint supports two mutually exclusive modes:

| Mode            | Parameter             | Behaviour                                                                            |
| --------------- | --------------------- | ------------------------------------------------------------------------------------ |
| **Bulk**        | `maxItems=1–500`      | Pullbay paginates Apple's feed internally and returns all results in one response    |
| **Single page** | `page=1–10` (or omit) | Returns \~50 reviews for that page, with `pagination.hasNextPage` so you can iterate |

`maxItems` and `page` cannot be used together in the same request.

### App Identifier Options

Each request requires exactly one identifier — `appId` (numeric) or `bundleId` (reverse-domain). They cannot be used together.

### Normalised Data

Review text, titles, and metadata are cleaned for consistency. Field names and types are stable across all responses.

### Structured Error Responses

Errors follow the same envelope as success responses, with a clear `error.code` and `message`.

***

## How to Fetch App Store Reviews: Practical Examples

You'll need:

* A Pullbay API key — get one free at [pullbay.com](https://pullbay.com/)
* An App Store numeric app ID (e.g. `284882215` for X/Twitter) or bundle ID (e.g. `com.atebits.Tweetie2`)

### cURL

```bash
curl "https://api.pullbay.com/appstore/reviews?appId=284882215&country=us&sort=recent&maxItems=50" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

### Python

```python
import requests

API_KEY = "YOUR_API_KEY"

response = requests.get(
    "https://api.pullbay.com/appstore/reviews",
    params={
        "appId":    "284882215",   # X/Twitter
        "country":  "us",
        "sort":     "recent",      # "recent" or "helpful"
        "maxItems": 50,
    },
    headers={"Authorization": f"Bearer {API_KEY}"},
    timeout=30,
)

body = response.json()
print(f"requestId: {body['requestId']}")
print(f"Retrieved {len(body['data'])} reviews — {body['pricing']['creditsCharged']} credits")

for review in body["data"]:
    print(f"{review['score']}⭐  {review['title']}")
    print(f"  {review['text'][:100]}...")
```

### Bulk Fetch — All Reviews in One Call

Use `maxItems=500` to get up to 500 reviews in a single request. Pullbay paginates Apple's feed internally and returns everything at once:

```python
import requests

API_KEY = "YOUR_API_KEY"

response = requests.get(
    "https://api.pullbay.com/appstore/reviews",
    params={
        "appId":    "284882215",
        "country":  "us",
        "maxItems": 500,
    },
    headers={"Authorization": f"Bearer {API_KEY}"},
    timeout=120,   # Allow time for internal pagination
)

body = response.json()
print(f"Total reviews: {len(body['data'])}")
print(f"Credits charged: {body['pricing']['creditsCharged']}")
print(f"requestId: {body['requestId']}")
```

### Page-by-Page Iteration

Use `page` when you want to process reviews incrementally or stop early:

```python
import requests

API_KEY = "YOUR_API_KEY"
all_reviews = []

for page in range(1, 11):   # Apple's feed caps at 10 pages
    response = requests.get(
    "https://api.pullbay.com/appstore/reviews",
        params={"appId": "284882215", "country": "us", "page": page},
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=30,
    )
    body = response.json()
    all_reviews.extend(body["data"])

    if not body["pagination"]["hasNextPage"]:
        break

print(f"Collected {len(all_reviews)} reviews")
```

***

## Understanding the API Response

Every Pullbay response follows the same envelope:

```json
{
  "requestId": "3b8dcb68-1f8c-4a7b-b9e7-63a7b9986f24",
  "status": 200,
  "message": "OK",
  "success": true,
  "data": [
    {
      "id": "13997548250",
      "date": "2026-04-25T19:57:33-07:00",
      "userName": "JohnDoe123",
      "userUrl": "https://itunes.apple.com/us/reviews/id1632846422",
      "score": 5,
      "title": "Best app for staying connected",
      "text": "Love the new features in the latest update!",
      "version": "10.2.1",
      "url": "https://itunes.apple.com/us/review?id=284882215&type=Purple%20Software",
      "country": "US"
    }
  ],
  "pagination": {
    "page": 1,
    "hasNextPage": true,
    "cursor": null,
    "offset": null
  },
  "pricing": {
    "creditsCharged": 52
  }
}
```

### Top-Level Fields

| Field                    | Description                                                                                                     |
| ------------------------ | --------------------------------------------------------------------------------------------------------------- |
| `requestId`              | Unique request identifier — log this for every call; it is the key piece of information when contacting support |
| `success`                | `true` on success, `false` on error                                                                             |
| `data`                   | Array of review objects                                                                                         |
| `pagination`             | Page state — only meaningful when using `page` mode; fields are `null` in bulk (`maxItems`) mode                |
| `pricing.creditsCharged` | Credits consumed by this request                                                                                |

### Review Object Fields

| Field      | Type        | Description                                                     |
| ---------- | ----------- | --------------------------------------------------------------- |
| `id`       | string      | Unique review identifier — use as primary key for deduplication |
| `score`    | integer 1–5 | Star rating                                                     |
| `title`    | string      | Review headline                                                 |
| `text`     | string      | Full review body                                                |
| `userName` | string      | Reviewer's App Store display name                               |
| `userUrl`  | string      | Link to reviewer's profile                                      |
| `date`     | string      | ISO 8601 datetime when the review was posted                    |
| `version`  | string      | App version the reviewer had installed                          |
| `url`      | string      | Direct link to the review (for developer reply)                 |
| `country`  | string      | 2-letter country code of the storefront                         |

### Pagination Fields

`pagination` is only meaningful in `page` mode. In `maxItems` (bulk) mode, all fields are `null`.

| Field         | Description                                                     |
| ------------- | --------------------------------------------------------------- |
| `hasNextPage` | `true` if more pages exist — use to control your iteration loop |
| `page`        | The page number returned                                        |

### Error Responses

```json
{
  "requestId": "3b8dcb68-1f8c-4a7b-b9e7-63a7b9986f24",
  "status": 400,
  "message": "Validation failed",
  "success": false,
  "error": { "code": "VALIDATION_ERROR" }
}
```

| HTTP Status | Error Code                    | Cause                                 |
| ----------- | ----------------------------- | ------------------------------------- |
| `400`       | `VALIDATION_ERROR`            | Invalid parameter name or value       |
| `401`       | `AUTHENTICATION_UNAUTHORIZED` | Invalid or missing API key            |
| `402`       | `TEAM_INSUFFICIENT_CREDITS`   | Credit balance is 0                   |
| `429`       | `RATE_LIMIT_EXCEEDED`         | Request rate exceeded for your plan   |
| `5xx`       | —                             | Server-side error; retry with backoff |

***

## Rate Limits

Rate limits are enforced per API key on a per-second and per-minute basis. Exact limits depend on your plan — check the dashboard under **Account → Plan**. Credits are charged per review returned, not per request.

Failed requests (any non-2xx response) do not consume credits.

***

## Frequently Asked Questions

<details>

<summary>Does Pullbay require App Store credentials?</summary>

No. You don't need an Apple Developer account or App Store credentials. Provide the public app ID (visible in the App Store URL) or bundle ID, and Pullbay handles the rest.

</details>

<details>

<summary>How fresh is the review data?</summary>

Data is fetched from Apple's review feed on demand when you make a request. Apple's feed has a natural lag of 1–24 hours from when a review is published to when it appears. This is an Apple limitation, not a Pullbay limitation. For monitoring, polling every 1–6 hours is sufficient for most use cases.

</details>

<details>

<summary>Can I get reviews for any country?</summary>

Yes. Use the `country` parameter with a 2-letter ISO 3166-1 alpha-2 code (e.g. `us`, `gb`, `jp`). The App Store has regional review feeds; Pullbay supports all active storefronts.

</details>

<details>

<summary>Can I filter reviews by date or rating?</summary>

No — the endpoint does not support `since`, `until`, or rating filter parameters. Fetch reviews and apply filters in your own code:

```python
# Filter to last 7 days
from datetime import datetime, timezone
cutoff  = datetime.now(timezone.utc).timestamp() - 7 * 86400
recent  = [r for r in body["data"] if datetime.fromisoformat(r["date"]).timestamp() > cutoff]

# Filter to low scores
low = [r for r in body["data"] if r["score"] <= 2]
```

</details>

<details>

<summary>What is the maximum number of reviews I can fetch?</summary>

Apple's review feed is limited to 10 pages of \~50 reviews each — a maximum of **500 reviews** per app per country storefront. Use `maxItems=500` to receive all of them in a single response.

</details>

<details>

<summary>What happens if a request fails?</summary>

Failed requests don't consume credits — you are only charged for successful `2xx` responses. If you receive a `5xx` error, retry with exponential backoff.

</details>

<details>

<summary>Can I use `bundleId` instead of `appId`?</summary>

Yes. `bundleId` (e.g. `com.atebits.Tweetie2`) is a valid alternative identifier. Pass exactly one of `appId` or `bundleId` — not both.

</details>

***

## Next Steps

1. **Get your API key** — sign up at [pullbay.com](https://pullbay.com/) and create a key from the dashboard
2. **Run your first request** — copy the cURL example above and replace `YOUR_API_KEY` with your actual key
3. **Set up monitoring** — see the Review Monitoring Guide for scheduling patterns, alerting, and database storage
4. **Build integrations** — see the n8n, Google Sheets, and Backend Integration guides for workflow examples

For questions or to report issues, contact <support@pullbay.com> and include the `requestId` from the error response.

***

## Key Takeaways

* The endpoint is `GET https://api.pullbay.com/appstore/reviews`
* Use `appId` (numeric) or `bundleId` (reverse-domain) — exactly one, never both
* Use `maxItems=1–500` for a bulk single-call fetch; use `page=1–10` to iterate page by page — never both in the same request
* Sort options are `recent` (default) and `helpful` only
* Review fields: `score` (not `rating`), `text` (not `content`), `userName` (not `author`)
* Credits are reported in `pricing.creditsCharged` — log it alongside `requestId` for every call
* Check `pagination.hasNextPage` (not `has_more`) when paginating
* Failed requests do not consume credits
* Maximum 500 reviews per app per country (Apple's feed limit)
