> 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-track-reviews-multiple-apps.md).

# How to Track Reviews Multiple Apps

If you manage a portfolio of apps, comparing app store reviews across your entire catalog is crucial. This guide shows you **how to track reviews multiple apps** efficiently, generate unified reports, and scale to dozens or hundreds of apps without hitting rate limits. Whether you're an agency, a studio with multiple titles, or a platform with many user-facing apps, this approach centralizes all review monitoring in one system.

## Multi-App Use Cases

Bulk review monitoring matters in several scenarios:

* **App portfolios**: Companies with 5, 10, or 50+ apps need a unified dashboard
* **Marketing/competitive analysis**: Monitor your own app suite plus competitors
* **Agencies**: Track client apps across multiple accounts
* **Regional versions**: Same app released in multiple countries with different app IDs
* **Legacy/new versions**: Monitor both the old and new version of your flagship app
* **Quality assurance**: Compare reviews before and after a major update

Let's build a system that handles all of these.

## The Multi-App Approach

The strategy:

1. Define a list of app IDs with human-readable names
2. Loop through each app and fetch reviews using Pullbay's managed pagination endpoint
3. Respect rate limits by adding small delays between requests
4. Consolidate all results into a single database or CSV with an `app_name` column
5. Generate a summary report showing metrics across all apps

## Step 1: Define Your App Portfolio

Create a configuration that maps app IDs to friendly names:

```python
APPS = {
    "123456789": "MyApp iOS",
    "987654321": "MyApp Pro",
    "111222333": "Competitor App A",
    "444555666": "Competitor App B"
}
```

## Step 2: Fetch Reviews for Each App

Loop through your app list and fetch reviews:

```python
import requests
import time

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

APPS = {
    "123456789": "MyApp iOS",
    "987654321": "MyApp Pro"
}

def fetch_app_reviews(app_id):
    """Fetch all reviews for a single app."""
    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 fetching app {app_id}: {response.status_code}")
        return []

    return response.json().get("data", [])

all_reviews = []

for app_id, app_name in APPS.items():
    print(f"Fetching reviews for {app_name}...")
    reviews = fetch_app_reviews(app_id)

    # Tag each review with the app name
    for review in reviews:
        review["_app_name"] = app_name
        review["_app_id"] = app_id

    all_reviews.extend(reviews)
    print(f"  Fetched {len(reviews)} reviews")

print(f"\nTotal reviews across all apps: {len(all_reviews)}")
```

## Step 3: Rate Limiting Between Requests

Respect Pullbay's rate limits by adding delays:

```python
import time

def fetch_app_reviews_with_limit(app_id, delay_seconds=1):
    """Fetch reviews with rate limiting."""
    response = requests.get(
        f"{BASE_URL}/app-store/reviews/all",
        params={"app_id": app_id},
        headers={"Authorization": f"Bearer {API_KEY}"}
    )

    # Check rate limit headers
    remaining = response.headers.get("X-RateLimit-Remaining")
    if remaining:
        remaining = int(remaining)
        print(f"  Rate limit remaining: {remaining}")

        # If getting close to limit, wait longer
        if remaining < 5:
            delay_seconds = 10

    time.sleep(delay_seconds)  # Small delay between requests
    return response.json().get("data", [])

# Usage
for app_id, app_name in APPS.items():
    print(f"Fetching {app_name}...")
    reviews = fetch_app_reviews_with_limit(app_id, delay_seconds=1)
    # ... process reviews
```

**Rate limit context**:

* **Free plan**: 10 requests/minute (fetch \~1 app per 6 seconds)
* **Starter plan**: 60 requests/minute (fetch \~1 app per second)
* **Growth/Scale**: Higher limits allow concurrent requests

## Step 4: Store Results with App Tagging

Store all reviews in a single CSV with an `app_name` column so you can filter by app later:

```python
import csv

OUTPUT_FILE = "all_apps_reviews.csv"

def export_all_reviews_to_csv(reviews):
    """Export all reviews from all apps to a single CSV."""
    if not reviews:
        print("No reviews to export")
        return

    with open(OUTPUT_FILE, "w", newline="", encoding="utf-8") as csvfile:
        fieldnames = [
            "app_name", "app_id", "date", "rating", "title",
            "content", "author", "version", "helpful_count"
        ]
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()

        for review in reviews:
            writer.writerow({
                "app_name": review.get("_app_name", ""),
                "app_id": review.get("_app_id", ""),
                "date": review.get("date", ""),
                "rating": review.get("rating", ""),
                "title": review.get("title", ""),
                "content": review.get("content", ""),
                "author": review.get("author", ""),
                "version": review.get("version", ""),
                "helpful_count": review.get("helpful_count", 0)
            })

    print(f"✓ Exported {len(reviews)} reviews to {OUTPUT_FILE}")
```

## Step 5: Generate Summary Report

Create a table showing metrics for each app:

```python
from datetime import datetime, timedelta

def generate_summary_report(reviews):
    """Generate a summary report across all apps."""
    if not reviews:
        print("No reviews to report")
        return

    # Group by app
    by_app = {}
    for review in reviews:
        app_name = review.get("_app_name", "Unknown")
        if app_name not in by_app:
            by_app[app_name] = []
        by_app[app_name].append(review)

    # Calculate metrics per app
    print("\n" + "=" * 100)
    print("MULTI-APP REVIEW SUMMARY")
    print("=" * 100)
    print(f"{'App Name':<30} {'Total':<8} {'Avg★':<8} {'5★':<8} {'1-2★':<8} {'Last 7d':<8}")
    print("-" * 100)

    cutoff = datetime.now() - timedelta(days=7)
    total_reviews = 0

    for app_name in sorted(by_app.keys()):
        app_reviews = by_app[app_name]
        total_reviews += len(app_reviews)

        # Average rating
        ratings = [r.get("rating", 3) for r in app_reviews]
        avg_rating = sum(ratings) / len(ratings) if ratings else 0

        # Count by rating
        five_star = sum(1 for r in app_reviews if r.get("rating") == 5)
        negative = sum(1 for r in app_reviews if r.get("rating", 3) <= 2)

        # Recent reviews (last 7 days)
        recent = sum(1 for r in app_reviews
                     if datetime.fromisoformat(r.get("date", "")) > cutoff)

        print(f"{app_name:<30} {len(app_reviews):<8} {avg_rating:<8.2f} {five_star:<8} {negative:<8} {recent:<8}")

    print("-" * 100)
    print(f"{'TOTAL':<30} {total_reviews:<8}")
    print("=" * 100 + "\n")
```

## Full Python Script: Multi-App Review Monitor

Here's the complete, production-ready script:

```python
import requests
import csv
import time
from datetime import datetime, timedelta

# Configuration
API_KEY = "live_your_api_key_here"
BASE_URL = "https://api.pullbay.com/v1"
OUTPUT_CSV = "all_apps_reviews.csv"

# Your app portfolio
APPS = {
    "123456789": "MyApp iOS",
    "987654321": "MyApp Pro",
    "111222333": "MyApp Lite",
    # Add more apps as needed
}

def fetch_app_reviews(app_id, app_name):
    """Fetch reviews for a single app with rate limiting."""
    print(f"Fetching {app_name} (ID: {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", [])
    meta = data.get("meta", {})

    print(f"  ✓ Fetched {len(reviews)} reviews (credits used: {meta.get('credits_used', '?')})")

    # Tag reviews with app info
    for review in reviews:
        review["_app_name"] = app_name
        review["_app_id"] = app_id

    return reviews

def export_to_csv(all_reviews):
    """Export all reviews to a single CSV with app_name column."""
    if not all_reviews:
        print("No reviews to export")
        return

    with open(OUTPUT_CSV, "w", newline="", encoding="utf-8") as csvfile:
        fieldnames = [
            "app_name", "app_id", "date", "rating", "title",
            "content", "author", "version", "helpful_count"
        ]
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()

        for review in all_reviews:
            writer.writerow({
                "app_name": review.get("_app_name", ""),
                "app_id": review.get("_app_id", ""),
                "date": review.get("date", ""),
                "rating": review.get("rating", ""),
                "title": review.get("title", ""),
                "content": review.get("content", ""),
                "author": review.get("author", ""),
                "version": review.get("version", ""),
                "helpful_count": review.get("helpful_count", 0)
            })

    print(f"✓ Exported {len(all_reviews)} reviews to {OUTPUT_CSV}")

def generate_summary_report(all_reviews):
    """Generate a summary report for each app."""
    if not all_reviews:
        return

    # Group by app
    by_app = {}
    for review in all_reviews:
        app_name = review.get("_app_name", "Unknown")
        if app_name not in by_app:
            by_app[app_name] = []
        by_app[app_name].append(review)

    # Print report
    print("\n" + "=" * 110)
    print("MULTI-APP REVIEW SUMMARY REPORT")
    print("Generated:", datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
    print("=" * 110)
    print(f"{'App Name':<35} {'Total':<10} {'Avg Rating':<12} {'5★':<8} {'1-2★':<8} {'Last 7d':<8}")
    print("-" * 110)

    cutoff = datetime.now() - timedelta(days=7)
    total_all = 0
    grand_avg = 0

    app_metrics = []
    for app_name in sorted(by_app.keys()):
        app_reviews = by_app[app_name]
        total_all += len(app_reviews)

        # Average rating
        ratings = [r.get("rating", 3) for r in app_reviews]
        avg_rating = sum(ratings) / len(ratings) if ratings else 0

        # Count by rating
        five_star = sum(1 for r in app_reviews if r.get("rating") == 5)
        negative = sum(1 for r in app_reviews if r.get("rating", 3) <= 2)

        # Recent reviews
        recent = sum(1 for r in app_reviews
                     if datetime.fromisoformat(r.get("date", "")) > cutoff)

        app_metrics.append({
            "name": app_name,
            "total": len(app_reviews),
            "avg": avg_rating,
            "five": five_star,
            "neg": negative,
            "recent": recent
        })

    # Print sorted by total reviews
    for metric in sorted(app_metrics, key=lambda x: x["total"], reverse=True):
        print(f"{metric['name']:<35} {metric['total']:<10} {metric['avg']:<12.2f} "
              f"{metric['five']:<8} {metric['neg']:<8} {metric['recent']:<8}")

    print("-" * 110)
    overall_avg = sum(r.get("rating", 3) for r in all_reviews) / len(all_reviews) if all_reviews else 0
    print(f"{'OVERALL':<35} {total_all:<10} {overall_avg:<12.2f}")
    print("=" * 110 + "\n")

def main():
    """Main: fetch reviews for all apps, export to CSV, and print report."""
    print(f"Multi-App Review Monitoring for {len(APPS)} apps\n")

    all_reviews = []
    for app_id, app_name in APPS.items():
        reviews = fetch_app_reviews(app_id, app_name)
        all_reviews.extend(reviews)
        time.sleep(1)  # Rate limiting: 1 second between requests

    print(f"\n✓ Total reviews across all apps: {len(all_reviews)}\n")

    # Export to CSV
    export_to_csv(all_reviews)

    # Print summary
    generate_summary_report(all_reviews)

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

**Running the script:**

```bash
python track_multiple_apps.py
```

**Output:**

```
Multi-App Review Monitoring for 3 apps

Fetching MyApp iOS (ID: 123456789)...
  ✓ Fetched 5,234 reviews (credits used: 8)
Fetching MyApp Pro (ID: 987654321)...
  ✓ Fetched 3,891 reviews (credits used: 6)
Fetching MyApp Lite (ID: 111222333)...
  ✓ Fetched 1,456 reviews (credits used: 2)

✓ Total reviews across all apps: 10,581

✓ Exported 10,581 reviews to all_apps_reviews.csv

======================================================== MULTI-APP REVIEW SUMMARY REPORT
Generated: 2024-04-13 10:45:32
======================================================== MULTI-APP REVIEW SUMMARY REPORT
App Name                            Total      Avg Rating   5★       1-2★     Last 7d
------================================================
MyApp iOS                           5,234      4.23         2,891    321      456
MyApp Pro                           3,891      4.15         1,834    389      234
MyApp Lite                          1,456      3.89         623      298      156
------================================================
OVERALL                             10,581     4.14
======================================================
```

## Scaling Tip: Async Requests for Large Portfolios

{% hint style="info" %}
If you have 20+ apps, make concurrent requests to speed things up using `asyncio` and `httpx`:
{% endhint %}

```python
import asyncio
import httpx

async def fetch_app_reviews_async(app_id, app_name, client):
    """Fetch reviews for a single app asynchronously."""
    response = await client.get(
        f"{BASE_URL}/app-store/reviews/all",
        params={"app_id": app_id}
    )
    reviews = response.json().get("data", [])
    for review in reviews:
        review["_app_name"] = app_name
        review["_app_id"] = app_id
    return reviews

async def fetch_all_apps_async():
    """Fetch reviews for all apps concurrently."""
    async with httpx.AsyncClient(
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=30
    ) as client:
        tasks = [
            fetch_app_reviews_async(app_id, app_name, client)
            for app_id, app_name in APPS.items()
        ]
        results = await asyncio.gather(*tasks)
        all_reviews = []
        for reviews in results:
            all_reviews.extend(reviews)
        return all_reviews

# Run async fetch
all_reviews = asyncio.run(fetch_all_apps_async())
```

This fetches all apps in parallel, much faster than sequential requests.

## Advanced: Diff Across Runs

Store snapshots of each run and compare to detect new reviews, rating changes, and deleted reviews:

```python
import json
from datetime import datetime

SNAPSHOT_FILE = "review_snapshot.json"

def save_snapshot(all_reviews):
    """Save current reviews as a snapshot."""
    snapshot = {
        "timestamp": datetime.now().isoformat(),
        "reviews": all_reviews
    }
    with open(SNAPSHOT_FILE, "w") as f:
        json.dump(snapshot, f)

def compare_snapshots(old_reviews, new_reviews):
    """Compare two snapshots to find changes."""
    old_ids = {r.get("id") for r in old_reviews}
    new_ids = {r.get("id") for r in new_reviews}

    added = new_ids - old_ids
    removed = old_ids - new_ids

    print(f"Reviews added: {len(added)}")
    print(f"Reviews removed: {len(removed)}")
```

## Troubleshooting

| Issue                      | Cause                                 | Solution                                                             |
| -------------------------- | ------------------------------------- | -------------------------------------------------------------------- |
| **Rate limit 429**         | Fetching too many apps too fast       | Increase `time.sleep()` delay; check dashboard for plan limits       |
| **Missing reviews in CSV** | App has no reviews                    | Verify the app ID exists on the App Store                            |
| **Duplicate app reviews**  | Running script twice without clearing | CSV is overwritten each run; for incremental updates, append instead |
| **Incomplete report**      | Some apps failed to fetch             | Script continues if one app fails; check console output for errors   |
| **Memory issues**          | Too many reviews for all apps         | Use SQLite instead of CSV; process apps one at a time                |

## Best Practices

* **Review naming**: Use a consistent naming scheme (`App Name - Country` or `App Name (v2)`)
* **Schedule regularly**: Run daily or weekly depending on your plan's rate limits
* **Archive reports**: Keep dated CSV files (`reviews_2024_04_13.csv`) for historical comparison
* **Set alerts**: Use the summary report to alert on sudden rating drops across your portfolio
* **Segment analysis**: Filter by country, version, or rating to identify regional issues

By centralizing your multi-app review monitoring, you'll spot portfolio-wide trends, identify which apps need attention, and make informed product decisions across your entire app suite.


---

# 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-track-reviews-multiple-apps.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.
