> 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. Organized by symptom with step-by-step fixes, code examples, and when to contact support.

## Authentication Issues

### Error: "401 Unauthorized"

The API is rejecting your credentials. Common causes and solutions:

{% stepper %}
{% step %}

### Check Your API Key Exists

* Visit the Pullbay dashboard → Account Settings → API Keys
* Verify your API key is listed and active (not revoked)
* Ensure you're using the correct key (test vs. live environments)
  {% endstep %}

{% step %}

### Verify API Key Format

* Test keys start with `test_`
* Live keys start with `live_`
* Your key should be exactly 32 characters after the prefix

```python
# ✓ Correct format
api_key = "test_abc123xyz789def0123456789ab"  # 32 chars
api_key = "live_xyz789def0123456789abcabc123"  # 32 chars

# ❌ Wrong format
api_key = "abc123"                              # Too short
api_key = "LIVE_abc123"                         # Wrong prefix case
```

{% endstep %}

{% step %}

### Check Authorization Header

Verify your Authorization header uses the correct format:

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

# ❌ Common mistakes:
headers = {"Authorization": "YOUR_API_KEY"}              # Missing "Bearer "
headers = {"Authorization": "Bearer"}                    # Missing key
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}"}         # Wrong case
```

{% endstep %}

{% step %}

### Ensure Key Is Not Revoked

If you recently rotated keys:

* Old keys are immediately revoked
* Update ALL applications and environments to use the new key
* Check your deployment pipelines for hardcoded old keys
  {% endstep %}

{% step %}

### Test Your Credentials

```bash
# Test with curl
curl -X GET "https://api.pullbay.com/v1/app-store/reviews/all?app_id=com.example&max_results=1" \
  -H "Authorization: Bearer YOUR_API_KEY"

# Should return 200 OK with data, or 400 if app_id invalid
# If you get 401, your API key is wrong
```

{% endstep %}
{% endstepper %}

***

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

This usually means you've revoked the old key but haven't updated all your code.

**Locations to Check:**

* [ ] Application code (main codebase)
* [ ] Environment variables (`.env`, `config.yml`, CloudFormation)
* [ ] Deployment scripts (GitHub Actions, Jenkins, GitLab CI)
* [ ] Docker images (rebuild and re-deploy)
* [ ] Serverless functions (Lambda, Cloud Functions)
* [ ] External services (Zapier, Make.com, n8n)
* [ ] Documentation and code examples

{% stepper %}
{% step %}

### Generate New Key

* Dashboard → API Keys → Create New Key
* Copy the key immediately (only shown once)
  {% endstep %}

{% step %}

### Update All Locations

```bash
# Search codebase for old key references
grep -r "test_old_key" .
grep -r "live_old_key" .
```

{% endstep %}

{% step %}

### Update Environment Variables

```bash
# .env file
PULLBAY_API_KEY=test_new_key_abc123xyz789def0123456789

# Or use secrets management:
# AWS Secrets Manager, Google Secret Manager, HashiCorp Vault, etc.
```

{% endstep %}

{% step %}

### Redeploy Applications

* Push code changes to version control
* Trigger CI/CD pipeline to rebuild and deploy
* Verify new deployment is live
  {% endstep %}

{% step %}

### Verify with Test Request

```python
import requests
import os

api_key = os.environ.get("PULLBAY_API_KEY")
response = requests.get(
    "https://api.pullbay.com/v1/app-store/reviews/all",
    params={"app_id": "com.example.app", "limit": 1},
    headers={"Authorization": f"Bearer {api_key}"}
)
assert response.status_code == 200, f"Auth failed: {response.status_code}"
print("✓ API key is working")
```

{% endstep %}
{% endstepper %}

## Credit Issues

### Error: "402 Payment Required" or "insufficient\_credits"

Your account has run out of API credits.

{% stepper %}
{% step %}

### Check Your Credit Balance

* Log in to Pullbay dashboard
* Dashboard → Billing → Current Balance
* Shows remaining API credits
  {% endstep %}

{% step %}

### Add Credits

* Dashboard → Billing → Add Credits
* Select credit package
* Complete payment
* Credits added immediately (usually within seconds)
  {% endstep %}

{% step %}

### Enable Auto-Reload (Recommended)

* Dashboard → Billing → Auto-Reload Settings
* Set threshold (e.g., "Reload when credits drop below 1000")
* Set reload amount (e.g., "Reload 5000 credits")
* Choose your preferred payment method
* Auto-reload happens automatically when threshold reached
  {% endstep %}

{% step %}

### Upgrade Your Plan

If you consistently run out of credits, consider upgrading:

See the [Plans page](broken://pages/3f7fe0ae21de214781d33c3abeb70b1a69c38601) or your [Pullbay dashboard](https://app.pullbay.com/) for current plan credit allocations and pricing.

Contact sales for custom enterprise agreements.
{% endstep %}
{% endstepper %}

***

### Error: "Credits depleted faster than expected"

Your credits are disappearing quickly. Investigate usage.

{% stepper %}
{% step %}

### Check Usage Breakdown

* Dashboard → Usage → Credit Usage by Endpoint
* Shows which endpoints consumed the most credits
* Identify high-consumption areas
  {% endstep %}

{% step %}

### Look for Managed Pagination Calls

Some endpoints use "managed pagination"—they automatically fetch all pages internally:

```python
# ❌ Expensive: Managed pagination fetches 100 pages internally
# Consumes 100 credits for 1 API call
response = requests.get(
    "https://api.pullbay.com/v1/app-store/reviews/all",
    params={
        "app_id": "com.example.app",
        "managed_pagination": True  # Fetches ALL pages
    },
    headers={"Authorization": f"Bearer {API_KEY}"}
)

# ✓ More efficient: Manual pagination, fetch only what you need
# Consumes 1 credit per call, you control how many
response = requests.get(
    "https://api.pullbay.com/v1/app-store/reviews/all",
    params={
        "app_id": "com.example.app",
        "limit": 50  # Get only 50 results
    },
    headers={"Authorization": f"Bearer {API_KEY}"}
)
```

{% endstep %}

{% step %}

### Reduce Redundant Requests

* Implement response caching (don't fetch same data repeatedly)
* Use longer cache TTLs for stable data
* Batch requests where possible
  {% endstep %}

{% step %}

### Monitor Requests Programmatically

```python
# Log each request's credit consumption
def log_credit_usage(response):
    credits_used = response.json().get("meta", {}).get("credits_used", 0)
    request_id = response.json().get("meta", {}).get("request_id")

    print(f"Request {request_id}: {credits_used} credits used")

    # Alert if single request uses too many credits
    if credits_used > 50:
        print(f"⚠️  High credit usage detected: {credits_used} credits")
```

{% endstep %}

{% step %}

### Enable Usage Alerts

* Dashboard → Billing → Usage Alerts
* Set alert thresholds (e.g., "Alert when monthly credits exceed 80,000")
* Choose notification method (email, webhook, etc.)
  {% endstep %}
  {% endstepper %}

## Rate Limit Issues

### Error: "429 Too Many Requests"

You've exceeded your plan's request rate limit.

{% stepper %}
{% step %}

### Check the X-RateLimit-Reset Header

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

reset_timestamp = int(response.headers.get("X-RateLimit-Reset"))
reset_time = datetime.fromtimestamp(reset_timestamp)
wait_seconds = reset_timestamp - time.time()

print(f"Rate limit resets at: {reset_time}")
print(f"Wait {wait_seconds:.1f} seconds before retrying")

# Wait for the reset time, then retry
time.sleep(wait_seconds + 1)
response = requests.get(...)  # Retry
```

{% endstep %}

{% step %}

### Check Your Current Rate Limit

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

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

print(f"Rate limit: {remaining}/{limit} requests remaining this minute")
print(f"Your plan allows: {limit} requests per minute")
```

{% endstep %}

{% step %}

### Implement Backoff Logic

Don't hammer the API. Implement exponential backoff:

```python
import time

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

        if response.status_code == 200:
            return response
        elif response.status_code == 429:
            wait_seconds = 2 ** attempt  # Exponential: 1, 2, 4, 8, 16 seconds
            print(f"Rate limited. Waiting {wait_seconds}s...")
            time.sleep(wait_seconds)
        else:
            raise Exception(f"Error: {response.status_code}")

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

{% endstep %}

{% step %}

### Upgrade Your Plan

If rate limits are blocking your application, upgrade:

| Plan    | Requests/Min |
| ------- | ------------ |
| Free    | 10           |
| Starter | 60           |
| Growth  | 300          |
| Scale   | 1000+        |

See the [Plans page](broken://pages/3f7fe0ae21de214781d33c3abeb70b1a69c38601) or your [Pullbay dashboard](https://app.pullbay.com/) for current pricing.
{% endstep %}
{% endstepper %}

***

### Error: "Hitting rate limits with sequential requests"

You're making requests too quickly.

{% stepper %}
{% step %}

### Implement Request Queuing

Process requests sequentially with delays:

```python
import time
import queue
import threading

class RequestQueue:
    def __init__(self, requests_per_second=5):
        self.delay = 1 / requests_per_second
        self.queue = queue.Queue()
        self.running = True

    def worker(self):
        while self.running:
            try:
                endpoint, params = self.queue.get(timeout=1)

                response = requests.get(
                    f"https://api.pullbay.com/v1{endpoint}",
                    params=params,
                    headers={"Authorization": f"Bearer {API_KEY}"}
                )

                # Process response
                print(f"✓ {endpoint}: {response.status_code}")

                self.queue.task_done()
                time.sleep(self.delay)  # Throttle
            except queue.Empty:
                continue

    def start(self):
        thread = threading.Thread(target=self.worker, daemon=True)
        thread.start()

# Usage
q = RequestQueue(requests_per_second=5)  # 5 requests per second = 300 per minute
q.start()

# Enqueue requests (they'll be processed sequentially)
q.queue.put(("/reviews/all", {"app_id": "com.example.app"}))
q.queue.put(("/reviews/all", {"app_id": "com.other.app"}))
```

{% endstep %}

{% step %}

### Use Managed Pagination

Fetch all data in fewer API calls:

```python
# ❌ Multiple API calls for pagination
cursor = None
while True:
    response = requests.get(
        "https://api.pullbay.com/v1/app-store/reviews",
        params={
            "app_id": "com.example.app",
            "cursor": cursor,
            "limit": 100
        },
        headers={"Authorization": f"Bearer {API_KEY}"}
    )
    # Process response
    if not response.json()["pagination"]["has_more"]:
        break
    cursor = response.json()["pagination"]["next_cursor"]

# ✓ Single API call with managed pagination
response = requests.get(
    "https://api.pullbay.com/v1/app-store/reviews/all",  # /all endpoint
    params={
        "app_id": "com.example.app",
        "managed_pagination": True  # Fetches all pages internally
    },
    headers={"Authorization": f"Bearer {API_KEY}"}
)
# All data in one call
```

{% endstep %}
{% endstepper %}

## Data Issues

### Problem: "Empty data array" (No results returned)

You're getting a successful response but no data.

{% stepper %}
{% step %}

### Verify the App ID is Valid

```python
# Check if app_id exists
response = requests.get(
    "https://api.pullbay.com/v1/app-store/reviews/all",
    params={"app_id": "com.example.app"},
    headers={"Authorization": f"Bearer {API_KEY}"}
)

if len(response.json()["data"]) == 0:
    print("No data found for this app")
    print("Verify app_id is correct and app exists")
```

Common issues:

* Typo in app\_id (e.g., `com.exampel.app` instead of `com.example.app`)
* App doesn't exist in the data source
* App has no reviews yet
  {% endstep %}

{% step %}

### Check App Availability by Country

Apps may only have data for certain countries:

```python
# Try default country (USA)
response = requests.get(
    "https://api.pullbay.com/v1/app-store/reviews/all",
    params={
        "app_id": "com.example.app",
        "country": "us"  # Explicitly specify
    },
    headers={"Authorization": f"Bearer {API_KEY}"}
)

if len(response.json()["data"]) == 0:
    print("No data for US. Try other countries.")
    # Try: uk, de, fr, ca, au, jp, etc.
```

{% endstep %}

{% step %}

### Check Filters Are Not Too Restrictive

```python
# Your filters might exclude all results
params = {
    "app_id": "com.example.app",
    "rating": 5,        # Only 5-star reviews
    "from_date": "2025-01-01"  # Future date = no results
}

# Try removing filters
params = {
    "app_id": "com.example.app",
    "limit": 10
}
```

{% endstep %}

{% step %}

### Verify Request Status

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

# Check if response is partial (timed out)
if data["meta"].get("status") == "partial":
    print(f"Partial response: {data['meta'].get('message')}")
    print(f"Received {len(data['data'])} results")
```

{% endstep %}
{% endstepper %}

***

### Problem: "Unexpected data schema" (Wrong field names)

The response doesn't match documentation.

{% stepper %}
{% step %}

### Check Endpoint Documentation

Different endpoints return different field names:

* `/reviews/all` returns: `id`, `author`, `rating`, `title`, `content`, `date`, etc.
* `/reviews/stats` returns: `average_rating`, `total_reviews`, `rating_distribution`, etc.

Verify you're using the correct endpoint and check the docs.
{% endstep %}

{% step %}

### Check if Response is Partial

```python
data = response.json()

if data["meta"].get("status") == "partial":
    print(f"Partial response: {data['meta'].get('message')}")
    print("Some fields may be missing")
    # Try again with smaller batch size
```

{% endstep %}

{% step %}

### Verify API Version

You're using `/v1` (versioned). Old code might expect different fields.
{% endstep %}
{% endstepper %}

## Pagination Issues

### Error: "Invalid cursor error"

Your pagination cursor is malformed or expired.

{% stepper %}
{% step %}

### Don't Modify Cursor Strings

Cursors are opaque tokens. Don't try to decode or modify them:

```python
# ❌ Wrong: Trying to parse cursor
next_cursor = response.json()["pagination"]["next_cursor"]
offset = json.loads(base64.decode(next_cursor))["offset"]  # Don't do this!

# ✓ Correct: Use cursor as-is
next_cursor = response.json()["pagination"]["next_cursor"]
response2 = requests.get(
    url,
    params={"cursor": next_cursor}  # Pass as-is
)
```

{% endstep %}

{% step %}

### Cursors May Expire

Cursors are only valid for a short time (usually minutes). If you get an invalid\_cursor error:

* Start pagination from the beginning (no cursor)
* Fetch all pages at once without delay
* Don't store cursors for later use

```python
# ❌ Don't: Save cursor and use later
cursor = response.json()["pagination"]["next_cursor"]
# ... hours later ...
response2 = requests.get(..., params={"cursor": cursor})  # May be expired

# ✓ Do: Fetch all pages immediately
cursor = None
all_data = []
while True:
    response = requests.get(..., params={"cursor": cursor})
    all_data.extend(response.json()["data"])

    if not response.json()["pagination"]["has_more"]:
        break
    cursor = response.json()["pagination"]["next_cursor"]
```

{% endstep %}

{% step %}

### Check has\_more Flag

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

if data["pagination"]["has_more"]:
    next_cursor = data["pagination"]["next_cursor"]
    # Fetch next page
else:
    # No more data - stop pagination
    print("Reached end of results")
```

{% endstep %}
{% endstepper %}

***

### Problem: "Managed endpoint times out"

Requests to managed pagination endpoints are taking too long.

{% stepper %}
{% step %}

### Increase Timeout

The Pullbay API can take up to 120 seconds for large result sets:

```python
# Python requests - increase timeout to 120 seconds
response = requests.get(
    url,
    params=params,
    headers=headers,
    timeout=120  # Give it more time
)
```

{% endstep %}

{% step %}

### Limit Data Size

If timeout persists, try limiting the data returned:

```python
# Instead of fetching all data at once
params = {
    "app_id": "com.example.app",
    "max_results": 10000  # Limit total results
}
```

{% endstep %}

{% step %}

### Use Manual Pagination Instead

If managed pagination times out, switch to manual pagination with smaller batches:

```python
# ✓ Manual pagination with smaller batches
cursor = None
all_data = []

while True:
    response = requests.get(
        "https://api.pullbay.com/v1/app-store/reviews",  # Manual pagination endpoint
        params={
            "app_id": "com.example.app",
            "limit": 100,      # Smaller batch
            "cursor": cursor
        },
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=30  # Shorter timeout per request
    )

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

    data = response.json()
    all_data.extend(data["data"])

    if not data["pagination"]["has_more"]:
        break
    cursor = data["pagination"]["next_cursor"]
```

{% endstep %}
{% endstepper %}

## n8n / Make.com Issues

### Problem: "n8n workflow fails on Pullbay node"

Common issues with n8n/Make.com integrations.

**1. Check Credential Setup**

* In n8n: Credentials → Create New → Pullbay
* Enter your API key exactly as provided (no extra spaces)
* Test the connection before using in workflow

**2. Verify Authentication Header** If using a custom HTTP Request node:

```
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```

**3. Increase HTTP Timeout** n8n's default timeout is short. For managed pagination:

* Edit HTTP Request node
* Options → Request Timeout: Set to 120 seconds
* This allows for large result sets

**4. Use /all Endpoints When Available** n8n has better support for managed pagination:

```
GET https://api.pullbay.com/v1/app-store/reviews/all?app_id=com.example.app
```

Instead of manual pagination loops.

**5. Handle Rate Limits in Workflow**

```
1. Make API request
2. Check status code = 429?
   - YES: Wait 60 seconds, retry
   - NO: Continue
3. Process response data
```

Use n8n's Wait node to implement backoff.

**6. Debug with Console Output** Add a Code node to log responses:

```javascript
// In n8n Code node
console.log("Response status:", $data[0].statusCode);
console.log("Response headers:", $data[0].headers);
console.log("Rate limit remaining:", $data[0].headers["x-ratelimit-remaining"]);

return $data;
```

## General Debugging Steps

Follow these steps in order to diagnose any issue:

{% stepper %}
{% step %}

### Note the Request ID

Every response (success or error) includes a `request_id` in the response body:

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

request_id = data["meta"]["request_id"]
print(f"Request ID: {request_id}")

# Save this - it uniquely identifies this request in Pullbay's logs
```

{% endstep %}

{% step %}

### Check Pullbay Service Health

* Visit the Pullbay status page (linked in your dashboard)
* If there's an outage or degradation, that explains it
* Implement retry logic and try again later
  {% endstep %}

{% step %}

### Verify Your API Key

```bash
# Test with curl
curl -X GET "https://api.pullbay.com/v1/app-store/reviews/all?app_id=com.example&max_results=1" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -v  # Verbose mode shows all headers

# Look for:
# - HTTP/1.1 200 OK (success) or HTTP/1.1 401 Unauthorized (auth failure)
# - Response body contains data or error message
```

{% endstep %}

{% step %}

### Check Your Credit Balance

```
Pullbay Dashboard → Billing → Current Balance
```

If you have 0 credits, all requests will be rejected with `402` status.
{% endstep %}

{% step %}

### Verify Your Request Parameters

```python
# Common mistakes:
params = {
    "app_id": "com.example.app",  # Required for most endpoints
    "limit": 100,                  # Optional, max 500
    "cursor": "...",              # Optional, for pagination
}

# Check against API documentation
# - Required parameters present?
# - Parameter types correct (string vs integer)?
# - Parameter values valid?
```

{% endstep %}

{% step %}

### Review Rate Limit Status

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

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

print(f"Rate limit: {remaining}/{limit} requests this minute")
print(f"Resets at: {datetime.fromtimestamp(int(reset))}")

if int(remaining) == 0:
    print("⚠️  Rate limit hit - wait until reset time")
```

{% endstep %}

{% step %}

### Check Service Health Status

If you're seeing 500-level errors:

* Visit Pullbay status page
* Check for known incidents
* If healthy, the error may be temporary—retry with backoff
  {% endstep %}

{% step %}

### Contact Pullbay Support

If still stuck, reach out to support with:

* [ ] The request\_id from the error response
* [ ] Your API key prefix (test\_ or live\_)
* [ ] The exact request you're making (parameters, endpoint)
* [ ] The error you're seeing (status code, error message)
* [ ] When the issue started
* [ ] Steps you've already tried

Include this information in your support ticket for faster resolution.
{% endstep %}
{% endstepper %}

## FAQ

<details>

<summary>How do I know if the issue is on my side or Pullbay's side?</summary>

Check response headers and status code:

* 4xx errors (except 429) are usually your problem (auth, parameters)
* 429 is rate limiting (your usage is too high)
* 5xx errors are Pullbay's problem (our servers)
* Network errors are usually transient

</details>

<details>

<summary>Can I test the API without an integration?</summary>

Yes, use curl:

```bash
curl -X GET "https://api.pullbay.com/v1/app-store/reviews/all?app_id=com.example&max_results=1" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

</details>

<details>

<summary>What's the best way to monitor Pullbay API health in production?</summary>

Implement:

* Request/response logging with timestamps
* Rate limit monitoring (check X-RateLimit-\* headers)
* Error rate alerts (if % of 5xx errors spikes)
* Credit usage alerts (if consumption is abnormal)

</details>

<details>

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

* Use `/all` endpoints for managed pagination (fewer API calls)
* Cache responses with appropriate TTL
* Batch logical requests
* Remove unnecessary parameters or filters

</details>

<details>

<summary>Is there a way to test in a sandbox before going live?</summary>

Yes, use test API keys (`test_` prefix). They work exactly like live keys but don't charge against your credit balance (charge rate depends on plan).

</details>


---

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