> 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/support/troubleshooting.md).

# Troubleshooting

Quick solutions to the most common Pullbay API problems. Organised by symptom with step-by-step fixes, code examples, and guidance on when to contact support.

***

## Authentication Issues

### Error: `401 Unauthorized`

The API is rejecting your credentials. Work through these checks in order.

**1. Verify the API key exists and is active**

* Go to the Pullbay dashboard → Account Settings → API Keys
* Confirm the key is listed and has not been revoked
* If you recently rotated keys, make sure every integration is using the new key

**2. Check the Authorization header format**

```python
# Correct
headers = {"Authorization": "Bearer YOUR_API_KEY"}

# Common mistakes
headers = {"Authorization": "YOUR_API_KEY"}              # Missing "Bearer "
headers = {"Authorization": "Bearer"}                    # Missing the key itself
headers = {"Api-Key": "YOUR_API_KEY"}                    # Wrong header name
headers = {"X-API-Key": "YOUR_API_KEY"}                  # Wrong header name
headers = {"Authorization": f"bearer {api_key}"}         # Lowercase "bearer" — must be "Bearer"
```

The header name must be `Authorization` (not `X-API-Key`, not `Api-Key`) and the value must start with `Bearer` followed by your key.

**3. Test with curl**

```bash
curl -X GET "https://api.pullbay.com/appstore/reviews?appId=389801252&maxItems=5" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

* `200 OK` with data → key is valid
* `400 Bad Request` with `VALIDATION_ERROR` → key is valid but a parameter is wrong
* `401 Unauthorized` → key itself is invalid or missing

***

### Error: "API key not working after rotation"

You have revoked the old key but not updated every place it was used.

**Locations to check:**

* [ ] Application code (main codebase)
* [ ] `.env` files and config files
* [ ] CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins)
* [ ] Docker images — rebuild and redeploy
* [ ] Serverless functions (AWS Lambda, Google Cloud Functions)
* [ ] External automation tools (n8n, Zapier, Make.com)

**Steps:**

1. Generate a new key: Dashboard → API Keys → Create New Key. Copy it immediately — it is only shown once.
2. Search for the old key in your codebase:

```bash
grep -r "OLD_KEY_VALUE" .
```

3. Update environment variables:

```bash
# .env
PULLBAY_API_KEY=new_key_here

# Or via secrets manager (AWS, GCP, Vault, etc.)
```

4. Redeploy, then verify:

```python
import requests
import os

api_key = os.environ.get("PULLBAY_API_KEY")
response = requests.get(
    "https://api.pullbay.com/appstore/reviews",
    params={"appId": "389801252", "maxItems": 1},
    headers={"Authorization": f"Bearer {api_key}"},
)
assert response.status_code == 200, f"Auth failed: {response.status_code}"
print("API key is working")
```

***

## Credit Issues

### Error: `402 Payment Required` / code `TEAM_INSUFFICIENT_CREDITS`

Your account has run out of API credits.

**1. Check your balance** — Dashboard → Billing → Current Balance

**2. Add credits** — Dashboard → Billing → Add Credits. Credits are added immediately after payment.

**3. Enable auto-reload** — set a threshold (e.g. reload 5,000 credits when balance drops below 1,000) so workflows never fail unexpectedly.

**4. Upgrade your plan** if you consistently run out — higher plans include larger monthly credit allocations. See the Pullbay dashboard for current plan options.

***

### Problem: Credits depleting faster than expected

**1. Check usage by endpoint** — Dashboard → Usage → Credit Usage Breakdown

**2. Understand how credits are charged**

Credits are charged per review returned, not per API call:

```
creditsCharged = reviews_returned × per_item_cost
```

`pricing.creditsCharged` in the response tells you exactly what each call cost:

```python
response = requests.get(
    "https://api.pullbay.com/appstore/reviews",
    params={"appId": "389801252", "maxItems": 500},
    headers={"Authorization": f"Bearer {API_KEY}"},
)
body = response.json()
print(f"requestId: {body['requestId']}")
print(f"Reviews returned: {len(body['data'])}")
print(f"Credits charged: {body['pricing']['creditsCharged']}")
```

**3. Reduce unnecessary fetches**

```python
# Expensive: fetches 500 reviews every hour whether new ones exist or not
# maxItems=500 → up to 500 credits per run × 24 runs/day

# Cheaper: fetch only one page (~50 reviews) to check for new content
params = {"appId": "389801252", "sort": "recent"}  # No maxItems → single page
```

**4. Cache responses** — avoid re-fetching the same data within minutes of the last call.

**5. Log credit consumption per call** — track spend over time and alert on anomalies:

```python
def call_api(params):
    response = requests.get(
        "https://api.pullbay.com/appstore/reviews",
        params=params,
        headers={"Authorization": f"Bearer {API_KEY}"},
    )
    body = response.json()
    print(
        f"requestId={body['requestId']} "
        f"reviews={len(body['data'])} "
        f"credits={body['pricing']['creditsCharged']}"
    )
    if body["pricing"]["creditsCharged"] > 200:
        print("Warning: high credit usage on this call")
    return body
```

***

## Rate Limit Issues

### Error: `429 Too Many Requests`

You have exceeded your plan's per-minute or per-second request limit.

**1. Read the rate limit headers**

```python
response = requests.get(...)

remaining = response.headers.get("X-RateLimit-Remaining")
limit      = response.headers.get("X-RateLimit-Limit")
reset_ts   = response.headers.get("X-RateLimit-Reset")

print(f"Remaining: {remaining}/{limit} requests this window")

if reset_ts:
    import time
    wait = int(reset_ts) - time.time()
    print(f"Resets in {wait:.1f}s")
```

**2. Implement exponential backoff**

```python
import time
import requests

def get_with_backoff(url, params, headers, max_retries=5):
    for attempt in range(max_retries):
        response = requests.get(url, params=params, headers=headers)

        if response.status_code == 200:
            return response

        if response.status_code == 429:
            wait = 2 ** attempt  # 1s, 2s, 4s, 8s, 16s
            print(f"Rate limited — waiting {wait}s (attempt {attempt + 1})")
            time.sleep(wait)
            continue

        response.raise_for_status()  # Raise on other errors

    raise RuntimeError("Max retries exceeded")
```

**3. Reduce request frequency**

Rate limits are per API key and depend on your plan. If you are consistently hitting limits, either reduce call frequency or upgrade to a plan with a higher rate limit.

***

### Problem: Hitting rate limits when processing multiple apps

**Option A — Add a delay between requests**

```python
import time

apps = ["389801252", "284882215", "310633997"]

for app_id in apps:
    response = requests.get(
        "https://api.pullbay.com/appstore/reviews",
        params={"appId": app_id, "maxItems": 100},
        headers={"Authorization": f"Bearer {API_KEY}"},
    )
    process(response.json())
    time.sleep(1)  # 1s gap between calls
```

**Option B — Use `maxItems` to reduce total call count**

Instead of paginating through many pages (one request per page), use `maxItems` to get up to 500 reviews in a single call:

```python
# Multiple calls (one per page)
for page in range(1, 11):
    response = requests.get(
        "https://api.pullbay.com/appstore/reviews",
        params={"appId": "389801252", "page": page},
        headers={"Authorization": f"Bearer {API_KEY}"},
    )
    ...

# Single call — Pullbay paginates internally
response = requests.get(
    "https://api.pullbay.com/appstore/reviews",
    params={"appId": "389801252", "maxItems": 500},
    headers={"Authorization": f"Bearer {API_KEY}"},
)
```

***

## Data Issues

### Problem: Empty `data` array

A `200 OK` response with `"data": []` means the request succeeded but no matching reviews exist.

**Check the app ID**

```python
# appId must be a numeric string, 8–12 digits
params = {"appId": "389801252", "maxItems": 5}  # Correct
params = {"appId": "com.burbn.instagram"}        # Wrong — use bundleId for bundle IDs
params = {"bundleId": "com.burbn.instagram"}     # Correct for bundle IDs
```

`appId` and `bundleId` are mutually exclusive — pass exactly one.

**Check the country**

Apps may have reviews in some storefronts but not others:

```python
# If US returns empty, try other countries
for country in ["us", "gb", "de", "fr", "au", "jp"]:
    response = requests.get(
        "https://api.pullbay.com/appstore/reviews",
        params={"appId": "389801252", "country": country, "maxItems": 5},
        headers={"Authorization": f"Bearer {API_KEY}"},
    )
    count = len(response.json()["data"])
    print(f"{country}: {count} reviews")
```

**Check the sort value**

Only `recent` and `helpful` are valid. Any other value causes a `400 VALIDATION_ERROR`.

***

### Problem: Wrong or unexpected field names

The response uses different field names than expected. The App Store review object fields are:

| Field      | Type    | Description                 |
| ---------- | ------- | --------------------------- |
| `id`       | string  | Unique review ID            |
| `date`     | string  | ISO 8601 datetime           |
| `userName` | string  | Reviewer display name       |
| `userUrl`  | string  | Link to reviewer profile    |
| `score`    | integer | Star rating 1–5             |
| `title`    | string  | Review headline             |
| `text`     | string  | Full review body            |
| `version`  | string  | App version reviewed        |
| `url`      | string  | Link to the review on Apple |
| `country`  | string  | 2-letter country code       |

Common mistakes:

```python
# Wrong field names from the response
review["author"]     # → use review["userName"]
review["rating"]     # → use review["score"]
review["content"]    # → use review["text"]
review["helpful_count"]  # → field does not exist
```

***

## Pagination Issues

### Problem: Getting only 50 reviews when you expect more

Without `maxItems`, the endpoint returns one page (\~50 reviews). Use `maxItems` to get up to 500 at once, or paginate with `page`:

```python
# Option 1 — Bulk fetch (up to 500 reviews, single call)
response = requests.get(
    "https://api.pullbay.com/appstore/reviews",
    params={"appId": "389801252", "maxItems": 500},
    headers={"Authorization": f"Bearer {API_KEY}"},
)
reviews = response.json()["data"]

# Option 2 — Manual page-by-page iteration
all_reviews = []
for page in range(1, 11):  # Max 10 pages
    response = requests.get(
        "https://api.pullbay.com/appstore/reviews",
        params={"appId": "389801252", "page": page},
        headers={"Authorization": f"Bearer {API_KEY}"},
    )
    body = response.json()
    all_reviews.extend(body["data"])
    if not body["pagination"]["hasNextPage"]:
        break
```

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

***

### Problem: Pagination loop never stops

Check `pagination.hasNextPage` (not `has_more` or `next`):

```python
all_reviews = []
page = 1

while True:
    response = requests.get(
        "https://api.pullbay.com/appstore/reviews",
        params={"appId": "389801252", "page": page},
        headers={"Authorization": f"Bearer {API_KEY}"},
    )
    body = response.json()
    all_reviews.extend(body["data"])

    if not body["pagination"]["hasNextPage"]:
        break  # No more pages

    page += 1
    if page > 10:
        break  # Apple's RSS feed has a hard limit of 10 pages
```

***

### Problem: Request with `maxItems` times out

`maxItems` causes Pullbay to fetch multiple Apple pages internally before responding — this can take up to 2 minutes for large values.

**Increase the client timeout:**

```python
response = requests.get(
    "https://api.pullbay.com/appstore/reviews",
    params={"appId": "389801252", "maxItems": 500},
    headers={"Authorization": f"Bearer {API_KEY}"},
    timeout=120,  # 2 minutes
)
```

**Or reduce `maxItems`** to fetch fewer pages internally:

```python
params={"appId": "389801252", "maxItems": 100}  # Faster than 500
```

**Or switch to page-by-page** with a shorter per-request timeout:

```python
for page in range(1, 11):
    response = requests.get(
        "https://api.pullbay.com/appstore/reviews",
        params={"appId": "389801252", "page": page},
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=30,
    )
    body = response.json()
    process(body["data"])
    if not body["pagination"]["hasNextPage"]:
        break
```

***

## n8n / Make.com Issues

### Problem: n8n workflow fails on the HTTP Request node

**1. Check credential setup**

In n8n: Credentials → Create New → Header Auth

| Field        | Value                 |
| ------------ | --------------------- |
| Header Name  | `Authorization`       |
| Header Value | `Bearer YOUR_API_KEY` |

Include the word `Bearer` and a space before the key.

**2. Check the URL**

The correct endpoint URL is:

```
https://api.pullbay.com/appstore/reviews
```

Common mistakes:

* `/app-store/` → should be `/appstore/` (no hyphen)
* `/reviews/all` → no such endpoint; use `maxItems` parameter instead

**3. Check parameter names** — use camelCase:

| Wrong         | Correct    |
| ------------- | ---------- |
| `app_id`      | `appId`    |
| `max_results` | `maxItems` |
| `bundle_id`   | `bundleId` |

**4. Increase the HTTP timeout**

Click **Additional Options** on the HTTP Request node and set **Timeout** to `120000` ms (2 minutes). Large `maxItems` values require extra time while Pullbay fetches pages internally.

**5. Read the right response fields in n8n**

| What you need   | n8n expression                       |
| --------------- | ------------------------------------ |
| Reviews array   | `{{ $json.data }}`                   |
| Credits charged | `{{ $json.pricing.creditsCharged }}` |
| Request ID      | `{{ $json.requestId }}`              |
| Has next page   | `{{ $json.pagination.hasNextPage }}` |
| Author name     | `{{ $json.userName }}`               |
| Score           | `{{ $json.score }}`                  |
| Review text     | `{{ $json.text }}`                   |

**6. Handle `429` with a Wait node**

Connect a `Wait` node (set to 60 seconds) after the HTTP Request node on the error path, then loop back to retry.

***

## General Debugging Steps

{% stepper %}
{% step %}

## Save the `requestId`

Every response (success and error) includes a top-level `requestId`. Log it for every call — it is the key piece of information when contacting support:

```python
body = response.json()
print(f"requestId: {body['requestId']}")
print(f"success:   {body['success']}")
if not body["success"]:
    print(f"error:     {body['error']['code']}")
```

{% endstep %}

{% step %}

## Check Pullbay service health

If you are seeing 5xx errors, check the Pullbay status page (linked in the dashboard) for known incidents before debugging your code.
{% endstep %}

{% step %}

## Reproduce with curl

Strip everything back to a minimal curl call to isolate whether the problem is in your code or the API:

```bash
curl -X GET \
  "https://api.pullbay.com/appstore/reviews?appId=389801252&maxItems=5" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

* `200` with data → API is fine; the bug is in your integration
* `401` → invalid API key
* `402` → insufficient credits
* `400` with `VALIDATION_ERROR` → a parameter name or value is wrong
* `5xx` → server-side issue; check the status page
  {% endstep %}

{% step %}

## Verify request parameters

```python
# appId: 8–12 digit numeric string
# bundleId: reverse-domain string (e.g. com.example.app)
# country: exactly 2 letters (e.g. "us", "gb")
# sort: "recent" or "helpful" only
# maxItems: integer 1–500
# page: integer 1–10
# maxItems and page are mutually exclusive
# appId and bundleId are mutually exclusive
```

{% endstep %}

{% step %}

## Check your credit balance

Dashboard → Billing → Current Balance. A balance of 0 causes every request to return `402`.
{% endstep %}

{% step %}

## Review rate limit headers

```python
print("Remaining:", response.headers.get("X-RateLimit-Remaining"))
print("Limit:",     response.headers.get("X-RateLimit-Limit"))
print("Reset at:",  response.headers.get("X-RateLimit-Reset"))
```

{% endstep %}

{% step %}

## Contact support

If still unresolved, open a support ticket and include:

* [ ] The `requestId` from the error response
* [ ] The exact endpoint and parameters you called
* [ ] The full error response body (status code + `error.code`)
* [ ] When the issue started
* [ ] Steps you have already tried
  {% endstep %}
  {% endstepper %}

## FAQ

<details>

<summary>How do I tell if the problem is my code or Pullbay's servers?</summary>

* `4xx` errors (except `429`) — your request is malformed (auth, parameters, credits)
* `429` — your request rate is too high for your plan
* `5xx` — server-side issue; check the status page and retry with backoff
* Network errors — usually transient; retry with backoff

</details>

<details>

<summary>What is a <code>requestId</code> and why does it matter?</summary>

Every response contains a `requestId` (also returned as the `X-Request-Id` response header). It uniquely identifies the request in Pullbay's logs. Always log it — without it, support cannot trace what happened.

</details>

<details>

<summary>How can I reduce credit consumption?</summary>

* Use `maxItems` only as large as you need — credits are charged per review returned
* Cache responses; avoid fetching the same app repeatedly within a short window
* Fetch a single page (no `maxItems`, no `page`) when you only need the latest reviews

</details>

<details>

<summary>Is there a sandbox or test mode?</summary>

There is no separate sandbox. To minimise credit usage during development, test with `maxItems=5` to fetch only 5 reviews and verify your workflow is correct before scaling up.

</details>

<details>

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

No — the App Store reviews endpoint does not support `from_date`, `to_date`, or `rating` filter parameters. Fetch reviews and apply date or score filters in your own code after receiving the response.

</details>
