> 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-data-guide.md).

# App Store Data Guide

When you fetch app reviews with Pullbay, you get rich structured data for each review. This guide explains every field in the response, what each one means, how to use it, and real-world examples. Whether you're building a dashboard, analysing sentiment, or filtering reviews by region, understanding your data is the foundation of smart review monitoring.

## The Complete App Store Review Object

Here's a full example response with every field explained:

```json
{
  "requestId": "3b8dcb68-1f8c-4a7b-b9e7-63a7b9986f24",
  "status": 200,
  "message": "OK",
  "success": true,
  "data": [
    {
      "id": "13997548250",
      "date": "2026-01-15T14:32:00-07:00",
      "userName": "ProductiveUser42",
      "userUrl": "https://itunes.apple.com/us/reviews/id1632846422",
      "score": 4,
      "title": "Great app but needs dark mode",
      "text": "I've been using this app for 2 years. It's excellent for productivity, but I really wish there was a dark mode option. My eyes hurt after using it in the evening. Otherwise, perfect!",
      "version": "10.2.1",
      "url": "https://itunes.apple.com/us/review?id=284882215&type=Purple%20Software",
      "country": "US"
    },
    {
      "id": "13997548251",
      "date": "2026-01-16T09:15:00-07:00",
      "userName": "FrustratedUser88",
      "userUrl": "https://itunes.apple.com/us/reviews/id9988776655",
      "score": 1,
      "title": "Crashes on startup",
      "text": "Updated to the latest version yesterday and now it won't even open. Just crashes immediately. Please fix ASAP!",
      "version": "10.3.0",
      "url": "https://itunes.apple.com/us/review?id=284882215&type=Purple%20Software",
      "country": "US"
    },
    {
      "id": "13997548252",
      "date": "2026-01-14T16:45:00-07:00",
      "userName": "BudgetConsciousUser",
      "userUrl": "https://itunes.apple.com/us/reviews/id1122334455",
      "score": 3,
      "title": "Good but expensive",
      "text": "The app works well, but the subscription cost is way too high compared to competitors.",
      "version": "10.2.0",
      "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": 3
  }
}
```

## Field Reference and Use Cases

### Envelope Fields

#### `requestId` (string)

Unique identifier for the API request itself — not the review. Log this for every call.

* **Use cases**: Debugging; include in support tickets so issues can be traced in logs

#### `pricing.creditsCharged` (integer)

Credits consumed by this request. Equals the number of reviews returned × the per-item cost for your task.

* **Use cases**: Credit monitoring; logging spend per call

#### `pagination.hasNextPage` (boolean)

Whether more pages exist. Only meaningful when using `page` mode — always `false` in `maxItems` (bulk) mode.

* **Use cases**: Loop control in page-by-page iteration

#### `pagination.page` (integer)

The page number returned.

### Review Object Fields

#### `id` (string)

Unique identifier for this review. Stable — never changes for the lifetime of the review.

* **Data type**: String (numeric string, e.g. `"13997548250"`)
* **Use cases**:
  * Primary key when storing reviews in your database
  * Deduplication: skip inserting a review if you've already seen this ID
  * Cross-referencing: link reviews to internal tickets, alerts, or threads

***

#### `score` (integer)

Star rating from 1 (lowest) to 5 (highest).

* **Data type**: Integer, values 1–5
* **Examples**: `1`, `3`, `5`
* **Use cases**:
  * Average rating: `sum(r["score"] for r in reviews) / len(reviews)`
  * Distribution: count reviews per star level
  * Sentiment filter: `[r for r in reviews if r["score"] <= 2]` for negative reviews
  * Trend detection: compare average score this week vs. last week
  * Release regression: if average score drops after a version release, investigate

#### `title` (string)

The review headline — typically 2–20 words, often summarises the main point.

* **Data type**: String
* **Examples**: `"Great app but needs dark mode"`, `"Crashes on startup"`
* **Use cases**:
  * First-pass triage: the title usually communicates the core issue before you read the full text
  * Keyword extraction: parse titles to identify recurring topics ("dark mode", "crash", "expensive")
  * Sentiment analysis: apply NLP to detect positive or negative language
  * Search: index titles to find reviews about specific topics

#### `text` (string)

The full review body — typically 1–500+ words with detailed feedback, bug descriptions, feature requests, and context.

* **Data type**: String (variable length, can span multiple paragraphs)
* **Example**: `"I've been using this app for 2 years. It's excellent for productivity, but I really wish..."`
* **Use cases**:
  * Bug detection: search for keywords like "crash", "freeze", "won't load", "error"
  * Feature request extraction: parse text for "please add", "would love", "feature request"
  * Customer support: read `text` to understand the user's exact problem in detail
  * Sentiment analysis and topic modelling: train ML models or use an LLM on review text

#### `userName` (string)

The App Store display name of the reviewer (pseudonymous — not a real name).

* **Data type**: String
* **Examples**: `"ProductiveUser42"`, `"FrustratedUser88"`
* **Use cases**:
  * Repeat reviewer detection: track if the same user reviews multiple times (may indicate a power user or chronic complainer)
  * Fake review detection: flag accounts posting many low-score reviews in rapid succession
  * Community building: identify engaged power users

#### `userUrl` (string)

Link to the reviewer's App Store profile page.

* **Data type**: String (URL)
* **Example**: `"https://itunes.apple.com/us/reviews/id1632846422"`
* **Use cases**: Direct link to the reviewer's profile for manual investigation

#### `date` (string, ISO 8601)

When the review was posted.

* **Data type**: String (ISO 8601 datetime with timezone offset)
* **Example**: `"2026-01-15T14:32:00-07:00"`
* **Use cases**:
  * Timeline correlation: link review spikes to app releases, marketing campaigns, or outages
  * Recency filtering: focus on reviews from the past 7 or 30 days
  * Trend analysis: group reviews by week, month, or release cycle
  * Regression detection: if average score drops on a specific date, check what changed

#### `version` (string)

The app version the reviewer had installed when they wrote the review.

* **Data type**: String (semantic version: `major.minor.patch`)
* **Examples**: `"10.2.1"`, `"10.3.0"`, `"9.1.2"`
* **Use cases**:
  * Release correlation: if reviews for `v10.3.0` have a lower average score, that version likely introduced a problem
  * Regression detection: compare `v10.2.1` vs. `v10.3.0` review sets to pinpoint what broke
  * Support prioritisation: identify whether a complaint applies to a still-supported version
  * Adoption tracking: see which versions your reviewers are running

#### `url` (string)

Direct link to the review on the App Store.

* **Data type**: String (URL)
* **Example**: `"https://itunes.apple.com/us/review?id=284882215&type=Purple%20Software"`
* **Use cases**: Open the review directly in a browser to write a developer reply

#### `country` (string)

Two-letter ISO 3166-1 alpha-2 country code for the storefront where the review was posted. Uppercase in the response.

* **Data type**: String (2-letter uppercase, e.g. `"US"`, `"GB"`)
* **Examples**: `"US"`, `"GB"`, `"DE"`, `"JP"`, `"AU"`, `"FR"`, `"IN"`
* **Use cases**:
  * Regional analysis: compare feedback between the US, UK, Germany, Japan
  * Localisation QA: spot region-specific bugs or translation issues
  * Launch monitoring: track reviews in new markets you've just expanded to
  * Market research: understand how different regions perceive your app

> **Note:** The `country` field in the response is uppercase (`"US"`). The `country` request parameter is lowercase (`"us"`). Both represent the same storefront.

## How to Filter and Query Reviews

### Filter by Score

```python
# Negative reviews (1–2 stars)
low = [r for r in reviews if r["score"] <= 2]
print(f"Negative reviews: {len(low)}")

# Positive reviews (4–5 stars)
high = [r for r in reviews if r["score"] >= 4]
print(f"Positive reviews: {len(high)}")
```

### Filter by Country (at Request Level)

Country filtering is applied at the API level via the `country` query parameter — each request returns reviews from one storefront:

```bash
# US App Store (default)
curl "https://api.pullbay.com/appstore/reviews?appId=284882215&country=us" \
  -H "Authorization: Bearer YOUR_API_KEY"

# Japan App Store
curl "https://api.pullbay.com/appstore/reviews?appId=284882215&country=jp" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```python
import requests

countries   = ["us", "gb", "de", "fr"]
by_country  = {}

for country in countries:
    body = requests.get(
        "https://api.pullbay.com/appstore/reviews",
        params={"appId": "284882215", "country": country, "maxItems": 100},
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=120,
    ).json()
    by_country[country] = body["data"]
    print(f"{country}: {len(body['data'])} reviews — {body['pricing']['creditsCharged']} credits")
```

### Filter by Version

```python
# Reviews for a specific version
v10_3_0 = [r for r in reviews if r["version"] == "10.3.0"]
avg      = sum(r["score"] for r in v10_3_0) / len(v10_3_0) if v10_3_0 else 0
print(f"v10.3.0 average score: {avg:.2f}")

# Compare two versions
def compare_versions(reviews, v1, v2):
    r1   = [r for r in reviews if r["version"] == v1]
    r2   = [r for r in reviews if r["version"] == v2]
    avg1 = sum(r["score"] for r in r1) / len(r1) if r1 else 0
    avg2 = sum(r["score"] for r in r2) / len(r2) if r2 else 0
    print(f"{v1}: {avg1:.2f}⭐ ({len(r1)} reviews)")
    print(f"{v2}: {avg2:.2f}⭐ ({len(r2)} reviews)")
    if avg2 < avg1 - 0.5:
        print(f"Warning: {v2} is scoring significantly lower than {v1}")
```

### Filter by Keyword

```python
def find_keyword(reviews, keyword):
    kw = keyword.lower()
    return [
        r for r in reviews
        if kw in r["title"].lower() or kw in r["text"].lower()
    ]

crash_reviews    = find_keyword(reviews, "crash")
dark_mode        = find_keyword(reviews, "dark mode")
pricing_mentions = find_keyword(reviews, "expensive")

print(f"Crash-related: {len(crash_reviews)}")
print(f"Dark mode requests: {len(dark_mode)}")
print(f"Pricing complaints: {len(pricing_mentions)}")
```

### Filter by Date

```python
from datetime import datetime, timezone, timedelta

cutoff  = datetime.now(timezone.utc) - timedelta(days=7)
recent  = [
    r for r in reviews
    if datetime.fromisoformat(r["date"]) > cutoff
]
print(f"Reviews in last 7 days: {len(recent)}")
```

## Pagination

### Bulk Fetch — `maxItems`

Use `maxItems` when you want all available reviews in a single call. Pullbay paginates Apple's feed internally and returns up to 500 reviews at once. No `pagination` fields are populated in the response.

```python
import requests

body = requests.get(
    "https://api.pullbay.com/appstore/reviews",
    params={"appId": "284882215", "country": "us", "maxItems": 500},
    headers={"Authorization": f"Bearer {API_KEY}"},
    timeout=120,
).json()

reviews = body["data"]
print(f"Total: {len(reviews)}, Credits: {body['pricing']['creditsCharged']}")
```

### Page-by-Page — `page`

Use `page` when you want to process results incrementally or stop early. Each page returns \~50 reviews.

```python
import requests

all_reviews = []
page        = 1

while True:
    body = requests.get(
        "https://api.pullbay.com/appstore/reviews",
        params={"appId": "284882215", "country": "us", "page": page},
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=30,
    ).json()

    all_reviews.extend(body["data"])
    print(f"Page {page}: {len(body['data'])} reviews — hasNextPage: {body['pagination']['hasNextPage']}")

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

    page += 1
    if page > 10:
        break  # Apple's feed is hard-capped at 10 pages

print(f"Total collected: {len(all_reviews)}")
```

`maxItems` and `page` cannot be used in the same request — choose one.

## Data Freshness

Data is fetched from Apple's review feed on demand. 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 most use cases — bug detection, trend monitoring, competitive analysis — polling every 1–6 hours is sufficient. For time-sensitive alerting (e.g. immediately catching a post-release spike), poll hourly and filter by `date` to process only reviews newer than your last run.

## Real-World Use Cases by Field

| Field      | Use Case                                         | Example                                                  |
| ---------- | ------------------------------------------------ | -------------------------------------------------------- |
| `id`       | Deduplication; link to internal tickets          | Store in DB with `INSERT OR IGNORE`                      |
| `score`    | Average score; detect rating drops               | "Average dropped to 3.2⭐ after v10.3.0"                  |
| `title`    | Quick triage; keyword extraction                 | "Crashes on startup" → alert engineering                 |
| `text`     | Detailed analysis; sentiment; feature extraction | Find all dark mode requests; parse bug descriptions      |
| `userName` | Repeat reviewer detection; fake review flags     | "User X posted 10 low-score reviews this week"           |
| `date`     | Timeline correlation                             | "Score spike on Jan 15 correlates with v10.2.1 release"  |
| `version`  | Regression detection; release health             | "v10.3.0 avg 2.1⭐ vs v10.2.1 avg 4.3⭐"                   |
| `country`  | Regional bugs; market research                   | "Germany has higher crash reports — localisation issue?" |
| `url`      | Direct link for developer reply                  | Open review in browser to write a response               |

## Common Questions

<details>

<summary>Can I request reviews for a specific date range?</summary>

No — the endpoint does not support `since` or `until` parameters. Fetch reviews and filter by `date` in your own code (see the date filter example above).

</details>

<details>

<summary>What if a review is edited or deleted?</summary>

When a user edits a review, the updated text appears on the next fetch. When a review is deleted, it no longer appears in the response. Pullbay does not track edit history or deletions — it always returns the current state from Apple's feed.

</details>

<details>

<summary>Can I export all historical reviews?</summary>

Yes. Use `maxItems=500` to fetch all reviews currently available in Apple's feed for an app. The maximum is 500 reviews per app per country storefront (Apple's RSS feed depth limit). Store results in your own database for long-term history and trend analysis.

</details>

<details>

<summary>How do I know how many credits a request consumed?</summary>

Every response includes `pricing.creditsCharged`. Log it alongside `requestId` for every call:

```python
body = response.json()
print(f"requestId: {body['requestId']}, credits: {body['pricing']['creditsCharged']}")
```

</details>

## Key Takeaways

* **Every field serves a purpose**: `score` and `text` tell you what the issue is; `version` links it to a code release; `country` reveals regional patterns; `date` enables timeline correlation
* **Use `score`**, not `rating` — use `text`, not `content` — use `userName`, not `author`
* **Use `id` as your primary key** for deduplication when storing reviews in a database
* **Pagination**: use `maxItems=500` for a single bulk call; use `page=1–10` to iterate page by page — never both together
* **Check `pagination.hasNextPage`** (not `has_more`) to control your iteration loop
* **Credits are reported in `pricing.creditsCharged`** — log it alongside `requestId` for every call
* **Date filtering is client-side** — fetch reviews and filter by `date` in your code; the API has no `since`/`until` parameters
* **Store reviews locally** for long-term trend analysis; Apple's feed only exposes the most recent 500 reviews per app per country
