> 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/how-to-guides-for-app-store/how-to-get-all-app-reviews.md).

# How to Get All App Reviews

Building a comprehensive review database is critical for long-term analysis, but the App Store's web interface only shows the most recent reviews. If you want **how to get all app store reviews** including your complete historical record, you need an API. This guide shows you two approaches using Pullbay: manual pagination (for fine-grained control) and managed pagination (for simplicity), plus strategies for storing large datasets and avoiding duplicates.

## The Challenge of Pagination

The App Store doesn't hand you all reviews in a single response. Instead, it returns them in pages. Manually requesting page after page is tedious and error-prone. Pullbay solves this with two options:

1. **Manual pagination**: You control the loop, useful for processing massive datasets incrementally
2. **Managed pagination**: Pullbay handles all pagination internally in a single call (recommended for most use cases)

## Approach 1: Managed Pagination (Recommended)

The simplest way to **fetch all app store reviews** is the `/v1/app-store/reviews/all` endpoint. Pullbay fetches every page for you and returns all results in one call:

```python
import requests

API_KEY = "live_your_api_key_here"
APP_ID = "123456789"
BASE_URL = "https://api.pullbay.com/v1"

response = requests.get(
    f"{BASE_URL}/app-store/reviews/all",
    params={"app_id": APP_ID},
    headers={"Authorization": f"Bearer {API_KEY}"}
)

data = response.json()
reviews = data.get("data", [])
print(f"Fetched {len(reviews)} reviews in a single call")
```

**When to use this**: Most cases. It's the easiest way to fetch all reviews once.

## Approach 2: Manual Pagination

If you're working with a massive app (100k+ reviews) and want to process page-by-page, use the standard `/v1/app-store/reviews` endpoint with cursor-based pagination:

```python
import requests

API_KEY = "live_your_api_key_here"
APP_ID = "123456789"
BASE_URL = "https://api.pullbay.com/v1"

all_reviews = []
cursor = None

while True:
    params = {"app_id": APP_ID}
    if cursor:
        params["cursor"] = cursor

    response = requests.get(
        f"{BASE_URL}/app-store/reviews",
        params=params,
        headers={"Authorization": f"Bearer {API_KEY}"}
    )

    data = response.json()
    reviews = data.get("data", [])
    all_reviews.extend(reviews)

    pagination = data.get("pagination", {})
    cursor = pagination.get("next_cursor")
    has_more = pagination.get("has_more", False)

    print(f"Fetched {len(reviews)} reviews. Has more: {has_more}")

    if not has_more:
        break

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

**When to use this**: Large apps where you want to process reviews incrementally and avoid loading all data into memory at once.

## Handling Large Datasets: Store to SQLite

For apps with thousands of reviews, writing to a database is smarter than keeping everything in memory. Here's how to use SQLite:

```python
import requests
import sqlite3
from datetime import datetime

API_KEY = "live_your_api_key_here"
APP_ID = "123456789"
BASE_URL = "https://api.pullbay.com/v1"
DB_FILE = "reviews.db"

def init_database():
    """Create the reviews table if it doesn't exist."""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS reviews (
            review_id TEXT PRIMARY KEY,
            app_id TEXT,
            author TEXT,
            rating INTEGER,
            title TEXT,
            content TEXT,
            date TEXT,
            version TEXT,
            helpful_count INTEGER
        )
    """)
    conn.commit()
    conn.close()

def insert_review(review):
    """Insert a single review into the database."""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    try:
        cursor.execute("""
            INSERT INTO reviews
            (review_id, app_id, author, rating, title, content, date, version, helpful_count)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
        """, (
            review.get("id"),
            APP_ID,
            review.get("author"),
            review.get("rating"),
            review.get("title"),
            review.get("content"),
            review.get("date"),
            review.get("version"),
            review.get("helpful_count", 0)
        ))
        conn.commit()
    except sqlite3.IntegrityError:
        # Review already exists
        pass
    finally:
        conn.close()

def fetch_and_store_all_reviews():
    """Fetch all reviews using managed pagination and store in SQLite."""
    init_database()

    print(f"Fetching reviews for app {APP_ID}...")
    response = requests.get(
        f"{BASE_URL}/app-store/reviews/all",
        params={"app_id": APP_ID},
        headers={"Authorization": f"Bearer {API_KEY}"}
    )

    if response.status_code != 200:
        print(f"Error: {response.status_code}")
        return

    data = response.json()
    reviews = data.get("data", [])

    print(f"Storing {len(reviews)} reviews...")
    for i, review in enumerate(reviews, 1):
        insert_review(review)
        if i % 1000 == 0:
            print(f"  Stored {i}/{len(reviews)}")

    print(f"✓ Stored {len(reviews)} reviews to {DB_FILE}")

if __name__ == "__main__":
    fetch_and_store_all_reviews()
```

## Deduplication: Avoid Duplicate Reviews

If you run the script multiple times, you'll get duplicates. Use the `review_id` as a primary key (shown above) or check before inserting:

```python
def review_exists(review_id):
    """Check if a review already exists in the database."""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    cursor.execute("SELECT 1 FROM reviews WHERE review_id = ?", (review_id,))
    exists = cursor.fetchone() is not None
    conn.close()
    return exists

def insert_review_if_new(review):
    """Insert only if the review doesn't already exist."""
    if not review_exists(review.get("id")):
        insert_review(review)
```

## Full Script: Fetch All Reviews, Store to SQLite, Print Summary

Here's a complete, production-ready script:

```python
import requests
import sqlite3
from datetime import datetime, timedelta

# Configuration
API_KEY = "live_your_api_key_here"
APP_ID = "123456789"
BASE_URL = "https://api.pullbay.com/v1"
DB_FILE = "reviews.db"

def init_database():
    """Create the reviews table."""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS reviews (
            review_id TEXT PRIMARY KEY,
            app_id TEXT,
            author TEXT,
            rating INTEGER,
            title TEXT,
            content TEXT,
            date TEXT,
            version TEXT,
            helpful_count INTEGER
        )
    """)
    conn.commit()
    conn.close()

def insert_review(review):
    """Insert a review, skipping duplicates."""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    try:
        cursor.execute("""
            INSERT INTO reviews
            (review_id, app_id, author, rating, title, content, date, version, helpful_count)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
        """, (
            review.get("id"),
            APP_ID,
            review.get("author"),
            review.get("rating"),
            review.get("title"),
            review.get("content"),
            review.get("date"),
            review.get("version"),
            review.get("helpful_count", 0)
        ))
        conn.commit()
        return True
    except sqlite3.IntegrityError:
        return False
    finally:
        conn.close()

def get_review_stats():
    """Calculate and print review statistics."""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()

    # Total reviews
    cursor.execute("SELECT COUNT(*) FROM reviews")
    total = cursor.fetchone()[0]

    # Average rating
    cursor.execute("SELECT AVG(rating) FROM reviews")
    avg_rating = cursor.fetchone()[0] or 0

    # Rating distribution
    cursor.execute("SELECT rating, COUNT(*) FROM reviews GROUP BY rating ORDER BY rating DESC")
    distribution = cursor.fetchall()

    # Date range
    cursor.execute("SELECT MIN(date), MAX(date) FROM reviews")
    min_date, max_date = cursor.fetchone()

    conn.close()

    print("\n" + "=" * 50)
    print("REVIEW STATISTICS")
    print("=" * 50)
    print(f"Total reviews: {total}")
    print(f"Average rating: {avg_rating:.2f}★")
    print(f"Date range: {min_date} to {max_date}")
    print("\nRating distribution:")
    for rating, count in distribution:
        pct = (count / total * 100) if total > 0 else 0
        stars = "★" * rating
        print(f"  {stars:<5} {rating}★: {count:6d} ({pct:5.1f}%)")
    print("=" * 50 + "\n")

def fetch_and_store_all_reviews():
    """Fetch all reviews and store in SQLite with progress tracking."""
    init_database()

    print(f"Fetching reviews for app ID: {APP_ID}")
    print(f"Using endpoint: {BASE_URL}/app-store/reviews/all\n")

    response = requests.get(
        f"{BASE_URL}/app-store/reviews/all",
        params={"app_id": APP_ID},
        headers={"Authorization": f"Bearer {API_KEY}"}
    )

    if response.status_code != 200:
        print(f"Error: {response.status_code} - {response.text}")
        return

    data = response.json()
    reviews = data.get("data", [])
    meta = data.get("meta", {})

    if not reviews:
        print(f"No reviews found for app {APP_ID}")
        return

    print(f"Received {len(reviews)} reviews from API")
    print(f"Credits used: {meta.get('credits_used', 'N/A')}\n")

    new_count = 0
    duplicate_count = 0

    print("Storing reviews...")
    for i, review in enumerate(reviews, 1):
        if insert_review(review):
            new_count += 1
        else:
            duplicate_count += 1

        if i % max(1, len(reviews) // 10) == 0 or i == len(reviews):
            pct = (i / len(reviews) * 100)
            print(f"  [{pct:3.0f}%] Processed {i}/{len(reviews)}")

    print(f"\n✓ Completed!")
    print(f"  New reviews inserted: {new_count}")
    print(f"  Duplicates skipped: {duplicate_count}")
    print(f"  Database file: {DB_FILE}")

    # Print stats
    get_review_stats()

if __name__ == "__main__":
    fetch_and_store_all_reviews()
```

**Running the script:**

```bash
python fetch_all_reviews.py
```

**Output:**

```
Fetching reviews for app ID: 123456789
Using endpoint: https://api.pullbay.com/v1/app-store/reviews/all

Received 5,234 reviews from API
Credits used: 15

Storing reviews...
  [10%] Processed 523/5234
  [20%] Processed 1046/5234
  ...
  [100%] Processed 5234/5234

✓ Completed!
  New reviews inserted: 5234
  Duplicates skipped: 0
  Database file: reviews.db

==================================================
REVIEW STATISTICS
==================================================
Total reviews: 5234
Average rating: 4.23★
Date range: 2023-01-15 to 2024-04-13

Rating distribution:
  ★★★★★ 5★:  2891 ( 55.2%)
  ★★★★   4★:  1156 ( 22.1%)
  ★★★     3★:   734 ( 14.0%)
  ★★       2★:   321 (  6.1%)
  ★         1★:   132 (  2.5%)
==================================================
```

## Why Complete History Matters

By building a complete review database, you can:

* **Trend analysis**: Track rating changes over months/years
* **Version correlation**: Compare reviews before/after a major release
* **Segment by country**: Identify region-specific issues
* **Archive for compliance**: Keep immutable records for regulatory requirements
* **Competitive benchmarking**: Store competitor reviews for analysis

## Troubleshooting

| Issue                | Cause                                            | Solution                                                         |
| -------------------- | ------------------------------------------------ | ---------------------------------------------------------------- |
| **Empty results**    | App has no reviews                               | Verify the app ID is correct; check the App Store directly       |
| **401 Unauthorized** | Invalid API key                                  | Check your key in the dashboard; ensure Bearer token is used     |
| **Slow performance** | Processing large datasets in memory              | Use the SQLite approach to stream data                           |
| **Database locked**  | Multiple scripts accessing SQLite simultaneously | Run fetches sequentially or use a dedicated database             |
| **Duplicates**       | Running script twice without deduplication       | Primary key (`review_id`) prevents duplicates; check constraints |

## Rate Limiting Considerations

* **Free plan**: 10 requests/minute
* **Starter plan**: 60 requests/minute
* **Growth plan**: 300 requests/minute
* **Scale plan**: 1000+ requests/minute

A single managed pagination call counts as 1 request, regardless of how many pages it fetches. Check the dashboard for your current plan and rate limits.

## Next Steps

* **Schedule regular pulls**: Run the script weekly or monthly to keep your database fresh
* **Export for analysis**: Query SQLite to export to CSV, Excel, or BI tools
* **Build dashboards**: Visualize trends over time using your stored data
* **Integrate with workflows**: Feed review data into your support ticket system

With a complete review history, you have the historical context to spot trends, measure the impact of releases, and make data-driven product decisions.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.pullbay.com/documentation/guides/how-to-guides-for-app-store/how-to-get-all-app-reviews.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
