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

# FAQ

Welcome to Pullbay! This FAQ covers common questions about getting started, billing, integrations, and more.

***

## Getting Started

<details>

<summary>What is Pullbay?</summary>

Pullbay is a data API platform that provides access to structured data from app stores, social platforms, and other services. Instead of scraping or building complex integrations yourself, Pullbay handles data fetching, normalisation, and delivery through a simple REST API.

**Use Pullbay for:**

* App Store reviews, ratings, and metadata
* App Store analytics and tracking
* Automating workflows with n8n, Make.com, and other automation platforms
* Building products that need App Store data

**Why Pullbay over scraping:**

* No scraping required — guaranteed data freshness
* Structured, standardised response format
* Reliable uptime and API support
* Credit-based pricing so you only pay for what you use

</details>

<details>

<summary>Do I need a credit card to sign up?</summary>

No. Pullbay offers a free tier with no credit card required. You get a credit allocation to explore the API. Upgrade to a paid plan when you are ready to scale.

</details>

<details>

<summary>How do I get my API key?</summary>

1. Create a Pullbay account at [pullbay.com](https://pullbay.com/)
2. Log in to your [dashboard](https://dashboard.pullbay.com/)
3. Navigate to **API Keys** in the left sidebar
4. Click **Create New Key**
5. Give the key a name (e.g. "Production", "Development")
6. Copy the key and store it securely — it is only shown once

**Never share your API key.** Treat it like a password. If you accidentally expose it, revoke it immediately from the API Keys page and create a new one.

</details>

<details>

<summary>Can I use multiple API keys?</summary>

Yes. You can create as many keys as you need from the dashboard:

1. Go to **API Keys** → **Create New Key**
2. Create separate keys for different applications, team members, environments (development, staging, production), or services

Each key has its own rate limit window. Revoking one key does not affect others — this is useful for isolating access and rotating keys safely.

</details>

<details>

<summary>How do I revoke an API key?</summary>

If you accidentally expose a key:

1. Go to **API Keys** in the dashboard
2. Find the exposed key
3. Click the three-dot menu → **Revoke**
4. The old key becomes invalid immediately
5. Update all applications to use a new key before revoking the old one

</details>

***

## Credits & Billing

<details>

<summary>How does credit billing work?</summary>

Pullbay uses a credit-based billing system:

1. **Sign up for a plan** — choose Free, Starter, Growth, or Scale
2. **Receive credits** — your plan includes a monthly credit allowance
3. **Use the API** — each successful request deducts credits based on the number of records returned
4. **Top up when needed** — add credits anytime from the dashboard
5. **Renew monthly** — plan credits reset on your billing date

`pricing.creditsCharged` in every API response tells you exactly how many credits that call consumed.

</details>

<details>

<summary>Are credits charged for failed requests?</summary>

No. Credits are only deducted for successful responses (`2xx`):

* `4xx` client errors (400, 401, 403, 404) — not charged
* `5xx` server errors (500, 502, 503) — not charged
* `429 Too Many Requests` — not charged
* Timeout errors — not charged

Only requests that return data consume credits.

</details>

<details>

<summary>Do credits expire?</summary>

**Plan credits** reset monthly — unused credits from the previous month are lost on your billing date.

**Purchased top-up credits** never expire. They are used after your monthly plan credits are exhausted and carry forward indefinitely.

**Strategy:** Rely on plan credits for predictable usage, and keep a top-up balance as a buffer for unexpected spikes.

</details>

<details>

<summary>What happens when I run out of credits?</summary>

When your balance reaches 0:

* New requests return `402 Payment Required` with error code `TEAM_INSUFFICIENT_CREDITS`
* No suspension — the API remains accessible, you just cannot make requests
* All your historical data and settings remain intact

To resume:

1. Dashboard → **Billing** → **Add Credits**
2. Choose an amount and complete payment
3. Credits are added immediately (usually within seconds)
4. Retry your requests

To prevent future outages, set up **auto-reload** in billing settings to automatically top up when your balance drops below a threshold.

</details>

<details>

<summary>Can I get a refund for credits?</summary>

Credits are non-refundable per Pullbay's Terms of Service. However:

* Unused credits persist in your account indefinitely (top-up credits)
* If a Pullbay error caused incorrect credit charges, contact support — the team may credit your account
* For special circumstances, email support with your account email, a description of the issue, and relevant `requestId` values

</details>

<details>

<summary>How much do credits cost?</summary>

Credit pricing and plan allocations are shown in the [Pullbay dashboard](https://dashboard.pullbay.com/) under **Billing**.

</details>

<details>

<summary>Which plan should I choose?</summary>

Choose based on your request volume and rate limit needs:

| Plan        | Best For                       |
| ----------- | ------------------------------ |
| **Free**    | Learning, testing, exploration |
| **Hobby**   | Small apps, side projects      |
| **Builder** | Production applications        |
| **Scale**   | High-volume, multiple apps     |

Start with the free tier and upgrade when you need higher rate limits or more monthly credits.

</details>

***

## Rate Limits

<details>

<summary>What are the rate limits?</summary>

Rate limits are enforced per API key on a per-second and per-minute basis. The exact limits depend on your plan. Check your current limits in the dashboard under **Account → Plan**.

When you exceed your rate limit, the API returns `429 Too Many Requests`. No credits are charged for rate-limited requests.

</details>

<details>

<summary>What happens when I hit the rate limit?</summary>

```http
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1729902400
```

```json
{
  "requestId": "3b8dcb68-1f8c-4a7b-b9e7-63a7b9986f24",
  "status": 429,
  "message": "Too Many Requests",
  "success": false,
  "error": { "code": "RATE_LIMIT_EXCEEDED" }
}
```

Read `X-RateLimit-Reset` to know when the window resets, then retry.

</details>

<details>

<summary>How do I handle rate limits in my code?</summary>

```python
import requests
import time

def call_with_retry(url, params, api_key, max_retries=5):
    for attempt in range(max_retries):
        response = requests.get(
            url,
            params=params,
            headers={"Authorization": f"Bearer {api_key}"},
            timeout=30,
        )

        if response.status_code == 200:
            return response.json()

        if response.status_code == 429:
            reset_ts = int(response.headers.get("X-RateLimit-Reset", 0))
            wait     = max(1, reset_ts - time.time() + 1)
            print(f"Rate limited — waiting {wait:.1f}s")
            time.sleep(wait)
            continue

        if response.status_code >= 500:
            wait = 2 ** attempt
            print(f"Server error — retrying in {wait}s")
            time.sleep(wait)
            continue

        response.raise_for_status()

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

**Pre-emptive check:** Monitor `X-RateLimit-Remaining` on every response and slow down before hitting 0.

</details>

<details>

<summary>Can I get a higher rate limit?</summary>

Yes:

* **Upgrade your plan** — each plan tier increases the rate limit
* **Contact sales** — custom limits are available for Scale customers

</details>

***

## App Store Service

<details>

<summary>What App Store data can I access?</summary>

Pullbay provides access to:

* **Reviews** — user reviews, scores, titles, text, reviewer name, version, country, date
* **App metadata** — name, category, release date, version, size, developer info, ratings, screenshots
* **Similar apps** — apps related to a given app
* **Developer apps** — all apps published by a developer
* **Search** — find apps by keyword

</details>

<details>

<summary>How do I find an app's ID?</summary>

Apps can be identified by their **numeric App Store ID** (`appId`) or their **bundle ID** (`bundleId`) — use exactly one per request.

**Find the numeric ID from the App Store URL:**

```
https://apps.apple.com/us/app/instagram/id389801252
                                               ↑
                                     appId: 389801252
```

**Search by keyword using the Pullbay search endpoint:**

```python
import requests

response = requests.get(
    "https://api.pullbay.com/appstore/search",
    params={"term": "Instagram", "country": "us"},
    headers={"Authorization": f"Bearer {API_KEY}"},
)

for app in response.json()["data"]:
    print(f"{app['title']}: appId={app['id']}, bundleId={app['appId']}")
```

**Common numeric IDs:**

* Instagram: `389801252`
* TikTok: `835599320`
* X (Twitter): `333903271`
* YouTube: `544007664`

</details>

<details>

<summary>What countries are supported for App Store reviews?</summary>

Reviews are available for any country supported by the App Store, specified as a 2-letter ISO 3166-1 alpha-2 code:

| Code | Country        |
| ---- | -------------- |
| `us` | United States  |
| `gb` | United Kingdom |
| `de` | Germany        |
| `fr` | France         |
| `jp` | Japan          |
| `au` | Australia      |
| `ca` | Canada         |
| `in` | India          |

Pass the code as the `country` parameter (default: `us`):

```python
response = requests.get(
    "https://api.pullbay.com/appstore/reviews",
    params={"appId": "389801252", "country": "gb", "sort": "recent"},
    headers={"Authorization": f"Bearer {API_KEY}"},
)
```

</details>

<details>

<summary>How fresh is the review data?</summary>

Data is fetched from Apple's API on demand when you make a request. There is no Pullbay-side cache — each call hits Apple directly.

In practice, Apple's own RSS feeds have a lag of 1–24 hours from when a review is published to when it appears in the feed. This is an Apple limitation, not a Pullbay limitation.

</details>

<details>

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

No — the App Store reviews endpoint does not support `since`, `until`, or rating filter parameters. Fetch reviews and apply any date or score filters in your own code:

```python
from datetime import datetime, timezone

response = requests.get(
    "https://api.pullbay.com/appstore/reviews",
    params={"appId": "389801252", "maxItems": 500, "sort": "recent"},
    headers={"Authorization": f"Bearer {API_KEY}"},
)

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

# Filter to last 7 days
cutoff = datetime.now(timezone.utc).timestamp() - 7 * 86400
recent = [r for r in reviews if datetime.fromisoformat(r["date"]).timestamp() > cutoff]

# Filter to low scores
low_score = [r for r in reviews if r["score"] <= 2]
```

</details>

<details>

<summary>What is the maximum number of reviews I can fetch?</summary>

The App Store review feed is limited to 10 pages of \~50 reviews each — a maximum of **500 reviews** per app per country.

Use `maxItems` (1–500) to receive all available reviews in a single response:

```python
response = requests.get(
    "https://api.pullbay.com/appstore/reviews",
    params={"appId": "389801252", "maxItems": 500},
    headers={"Authorization": f"Bearer {API_KEY}"},
    timeout=120,  # Allow time for internal pagination
)
reviews = response.json()["data"]  # Up to 500 reviews
```

</details>

***

## Pagination

<details>

<summary>What are the two fetch modes for reviews?</summary>

The `/api/appstore/reviews` endpoint supports two mutually exclusive modes:

| Mode            | Parameter             | Behaviour                                                                                                        |
| --------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------- |
| **Bulk**        | `maxItems=1–500`      | Pullbay paginates Apple's API internally and returns all results in one response. No pagination fields returned. |
| **Single page** | `page=1–10` (or omit) | Returns \~50 reviews for that page with `pagination.hasNextPage` and `pagination.page`.                          |

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

</details>

<details>

<summary>How do I paginate page-by-page?</summary>

```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}"},
        timeout=30,
    )
    body = response.json()
    all_reviews.extend(body["data"])

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

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

</details>

<details>

<summary>Can I resume pagination if my request fails?</summary>

Yes. Because the App Store uses page-based pagination, store the last successfully processed page number and resume from there:

```python
import json
import os

STATE_FILE = "pagination_state.json"

def load_state():
    if os.path.exists(STATE_FILE):
        return json.load(open(STATE_FILE))
    return {"last_page": 0}

def save_state(page):
    json.dump({"last_page": page}, open(STATE_FILE, "w"))

state    = load_state()
start    = state["last_page"] + 1
reviews  = []

for page in range(start, 11):
    try:
        response = requests.get(
            "https://dashboard.pullbay.com/api/appstore/reviews",
            params={"appId": "389801252", "page": page},
            headers={"Authorization": f"Bearer {API_KEY}"},
            timeout=30,
        )
        body = response.json()
        reviews.extend(body["data"])
        save_state(page)  # Save progress

        if not body["pagination"]["hasNextPage"]:
            break
    except Exception as e:
        print(f"Failed on page {page}: {e}")
        break  # Next run resumes from saved page
```

</details>

***

## Integrations

<details>

<summary>Can I use Pullbay with n8n?</summary>

Yes. Use the **HTTP Request** node with these settings:

* **Method:** GET
* **URL:** `https://api.pullbay.com/appstore/reviews`
* **Authentication:** Header Auth (`Authorization: Bearer YOUR_API_KEY`)
* **Query Parameters:** `appId`, `country`, `sort`, `maxItems` (or `page`)

See the n8n Integration Guide for complete workflow examples including Google Sheets sync, Slack alerts, and sentiment analysis.

</details>

<details>

<summary>Can I use Pullbay with Make.com or Zapier?</summary>

Use the **HTTP module** (Make.com) or **Webhooks by Zapier** with the same endpoint and parameters as above. Dedicated Make.com and Zapier modules are in development.

</details>

<details>

<summary>Should I call Pullbay from client-side code?</summary>

**No — never from client-side JavaScript (browser or mobile app).**

If your API key is in client-side code, anyone can view it in browser DevTools, steal your key, and consume your credits.

**Correct architecture:**

```
Browser → Your backend → Pullbay API
```

Your backend holds the API key and proxies requests:

```javascript
// backend — api key stays on the server
app.get("/api/reviews/:appId", async (req, res) => {
  const response = await fetch(
    `https://api.pullbay.com/appstore/reviews?appId=${req.params.appId}&maxItems=50`,
    { headers: { Authorization: `Bearer ${process.env.PULLBAY_API_KEY}` } }
  );
  res.json(await response.json());
});
```

```javascript
// frontend — no API key here
fetch("/api/reviews/389801252")
  .then(r => r.json())
  .then(data => console.log(data.data));
```

</details>

***

## Security & Compliance

<details>

<summary>Is the data Pullbay provides legal to use?</summary>

Pullbay provides access to publicly available data. However, **you are responsible** for ensuring your specific use case complies with Apple's Terms of Service and applicable law.

Generally acceptable uses: analytics, app monitoring, market research, competitor analysis.

Consult your legal team for commercial use cases involving large-scale data collection or redistribution.

</details>

<details>

<summary>How do I keep my API key secure?</summary>

**Do:**

* Store in environment variables — never in source code
* Use a secrets manager in production (AWS Secrets Manager, Google Secret Manager, HashiCorp Vault)
* Create separate keys per application and environment
* Revoke and rotate keys regularly

**Never:**

* Commit a key to version control
* Share in email, Slack, or chat
* Include in client-side JavaScript, mobile app bundles, or public repositories
* Hardcode in any source file

```python
import os
api_key = os.environ["PULLBAY_API_KEY"]  # Correct
api_key = "live_abc123..."               # Never do this
```

</details>

<details>

<summary>Does Pullbay store the data I fetch?</summary>

No. Data is fetched from upstream sources (Apple's API) on demand and returned directly to you. Pullbay does not persist the content of your responses.

Pullbay does store:

* API usage logs (request timestamp, endpoint, status code, credits charged) — for billing and support
* Your account settings and API keys
* Aggregated usage metrics

Usage logs are retained for billing and support purposes (typically 90 days).

</details>

***

## Support

<details>

<summary>How do I contact support?</summary>

**Email:** <support@pullbay.com>

Include in your request:

* The `requestId` from the error response (this is the fastest way for support to find your request in logs)
* The endpoint and parameters you called
* The full error response (status code + `error.code`)
* Steps to reproduce
* What you expected vs. what happened

**Response times:**

| Plan    | Response Time            |
| ------- | ------------------------ |
| Free    | 2–3 business days        |
| Starter | 1 business day           |
| Growth  | 24 hours                 |
| Scale   | 2 hours (business hours) |

</details>

<details>

<summary>What should I include in a support request?</summary>

The `requestId` field is returned in every response — success and error alike. It is also available as the `X-Request-Id` response header. Log it for every API call:

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

**Example support request:**

```
Subject: 402 error on reviews endpoint — unexpected credit depletion

requestId: 3b8dcb68-1f8c-4a7b-b9e7-63a7b9986f24
Endpoint: GET https://api.pullbay.com/appstore/reviews
Params:   appId=389801252, maxItems=500

Error: HTTP 402
Body: {"error": {"code": "TEAM_INSUFFICIENT_CREDITS"}}

Steps to reproduce:
1. Ran 20 requests this morning, all with maxItems=500
2. Request #18 returned 402

Expected: requests to succeed
Actual: balance depleted faster than expected

Can you help me understand how many credits were consumed per call?
```

</details>

<details>

<summary>What if I have an urgent production issue?</summary>

Email support immediately with **"URGENT"** in the subject line. Include:

* The `requestId` values showing the issue
* Current customer impact (how many users affected)
* Business impact
* Any workaround you have in place

Scale plan customers may have access to a priority support channel — ask your account manager.

</details>

***

## Troubleshooting

<details>

<summary>I am getting `401 Unauthorized`</summary>

**Cause:** Authentication failed.

**Fix:**

1. Verify your key exists and is active: Dashboard → API Keys
2. Check the header format — it must be exactly `Authorization: Bearer YOUR_KEY` (capital B, space after Bearer)
3. Test with curl:

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

</details>

<details>

<summary>I am getting `402 Payment Required`</summary>

**Cause:** Your credit balance is 0.

**Fix:**

1. Check balance: Dashboard → Billing → Current Balance
2. Add credits: Dashboard → Billing → Add Credits
3. Credits are added immediately — retry your request

</details>

<details>

<summary>I am getting `429 Too Many Requests`</summary>

**Cause:** You exceeded your plan's rate limit.

**Fix:**

1. Read `X-RateLimit-Reset` from the response header and wait until that timestamp
2. Implement exponential backoff (see Rate Limits section)
3. Upgrade your plan for a higher rate limit

</details>

<details>

<summary>I am getting `400 Bad Request` with `VALIDATION_ERROR`</summary>

**Cause:** A parameter name or value is invalid.

**Common mistakes:**

| Wrong                         | Correct                               |
| ----------------------------- | ------------------------------------- |
| `app_id`                      | `appId`                               |
| `max_results`                 | `maxItems`                            |
| `bundle_id`                   | `bundleId`                            |
| `sort: "critical"`            | `sort: "recent"` or `sort: "helpful"` |
| `appId` + `bundleId` together | use exactly one                       |
| `maxItems` + `page` together  | use exactly one                       |

</details>

<details>

<summary>I am getting `5xx` server errors</summary>

**Cause:** A Pullbay server issue.

**Fix:**

1. Check the Pullbay status page for known incidents: [status.pullbay.com](https://status.pullbay.com/)
2. Retry with exponential backoff
3. Contact support if errors persist — include the `requestId`

</details>

<details>

<summary>Requests are timing out</summary>

**Cause:** The request took longer than your client timeout.

**Fix:**

* For `maxItems=500`: set client timeout to at least `120` seconds — Pullbay internally fetches up to 10 Apple pages
* For single-page requests (`page=N`): `30` seconds is sufficient

```python
response = requests.get(url, params=params, headers=headers, timeout=120)
```

</details>

<details>

<summary>I am getting an empty `data` array</summary>

**Cause:** The request succeeded but no reviews match.

**Check:**

1. `appId` is a valid 8–12 digit numeric string (or use `bundleId` for bundle IDs)
2. The app has reviews in the requested `country`
3. You are not passing both `appId` and `bundleId` in the same request

```python
# Try the US store first; if empty, try other countries
for country in ["us", "gb", "au", "ca"]:
    body = requests.get(
        "https://api.pullbay.com/appstore/reviews",
        params={"appId": "389801252", "country": country, "maxItems": 5},
        headers={"Authorization": f"Bearer {API_KEY}"},
    ).json()
    print(f"{country}: {len(body['data'])} reviews")
```

</details>

***

## Miscellaneous

<details>

<summary>How do I report a bug?</summary>

Email <support@pullbay.com> with:

* Description of the unexpected behaviour
* Steps to reproduce
* Expected vs. actual result
* `requestId` values (if API-related)

</details>

<details>

<summary>Can I request a feature?</summary>

Yes — use the feedback form in the dashboard or email support. Include the feature description, your use case, and how it would improve your workflow.

</details>

<details>

<summary>Where can I find API documentation?</summary>

Full endpoint documentation, request schemas, and response examples are available in the [Pullbay dashboard](https://dashboard.pullbay.com/) under **Docs**.

</details>

<details>

<summary>How do I stay updated?</summary>

* **Status page:** [status.pullbay.com](https://status.pullbay.com/) — incidents and maintenance
* **Dashboard notifications:** major feature releases are announced in the dashboard
* **Email:** subscribe to release notes under Dashboard → Settings → Email Preferences

</details>

***

## Still have questions?

**Email:** <support@pullbay.com>

Include the `requestId` from your error response — it lets support find your request in logs immediately and resolve issues much faster.
