> 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/platform-best-practices/cost-optimization-revisit.md).

# Cost Optimization (REVISIT)

### Overview

Pullbay's credit-based pricing model rewards efficient API usage. This guide provides 7 proven optimization strategies with code examples to reduce your monthly credit costs by 50-90%. Whether you're just starting or scaling to thousands of requests, these techniques apply to every usage pattern.

***

### Understand Your Usage First

Before optimizing, understand where you're spending credits. Most of Pullbay's dashboard provides a usage breakdown by endpoint.

**Steps to analyze your usage:**

1. Log into your Pullbay dashboard
2. Navigate to **Usage & Analytics**
3. View breakdown by endpoint, date range, and request volume
4. Identify top-spending endpoints (usually reviews, search results, business info)
5. Look for spikes or anomalies

**Questions to ask:**

* Which endpoints consume the most credits?
* Are there spikes at certain times?
* How many requests do you make per day?
* How many pages per request do you typically fetch?

Once you identify patterns, apply the optimization strategies below to your top-spending endpoints first.

***

### 7 Optimization Strategies

#### Strategy 1: Fetch Only What You Need (Best ROI)

**Impact: 50-90% credit savings**

This single strategy delivers the highest return on investment. Stop fetching data you don't use.

**Problem: Over-fetching**

```python
# BAD: Fetches ALL 5,000 reviews when you only display 50
all_reviews = []
for review in paginator.paginate("reviews", params={"business_id": "12345"}):
    all_reviews.append(review)

# Later: use only the first 50 for your dashboard
recent_reviews = all_reviews[:50]

print(f"Fetched {len(all_reviews)}, but used only {len(recent_reviews)}")
# Output: Fetched 5000, but used only 50 (99% wasted credits!)
```

**Solution: Fetch with a Limit**

```python
# GOOD: Stop after 50 reviews
recent_reviews = []
for review in paginator.paginate(
    "reviews",
    params={"business_id": "12345"}
):
    recent_reviews.append(review)

    if len(recent_reviews) >= 50:
        break  # Stop here!

print(f"Fetched exactly {len(recent_reviews)} reviews")
# Output: Fetched exactly 50 reviews (100% efficient)
```

Or use built-in `max_results` parameter:

```python
# BEST: Uses built-in limit (stops at API level)
recent_reviews = list(paginator.paginate(
    "reviews",
    params={"business_id": "12345"},
    max_results=50  # API stops fetching after 50
))
```

**Real-world Savings Example**

| Scenario                     | Without Limit        | With Limit          | Savings |
| ---------------------------- | -------------------- | ------------------- | ------- |
| Dashboard display (50 items) | 5,000 items fetched  | 50 items fetched    | 99%     |
| Weekly export (500 items)    | 10,000 items fetched | 500 items fetched   | 95%     |
| Monthly report (1,000 items) | 2,000 items fetched  | 1,000 items fetched | 50%     |

**Annual credit example:**

* 10 apps × 40 pages × 30 days = **12,000 credits/month** (full fetch)
* With 50-item limit per app (from 5,000 items): **240 credits/month**
* **Savings: \~11,760 credits/month (98%)**

**When to use this strategy:**

* Displaying top-N results (top 10 reviews, top 50 search results)
* Sampling data (get 100 items to test quality)
* Pagination in web applications (show 50 per page, fetch more on demand)
* Any time you know your maximum needed result count

***

#### Strategy 2: Cache Responses (50% Credit Savings with 50% Hit Rate)

**Impact: 30-70% credit savings**

Cache API responses so repeated requests don't consume credits.

**Simple TTL-based Caching**

```python
import json
import time
import os
from datetime import datetime, timedelta

class ReviewCache:
    def __init__(self, cache_dir="./cache", ttl_seconds=3600):
        self.cache_dir = cache_dir
        self.ttl = ttl_seconds
        os.makedirs(cache_dir, exist_ok=True)

    def _cache_key(self, business_id):
        """Generate cache filename."""
        return os.path.join(self.cache_dir, f"reviews_{business_id}.json")

    def get(self, business_id):
        """Get cached reviews if valid."""
        cache_file = self._cache_key(business_id)

        if not os.path.exists(cache_file):
            return None

        # Check if cache is still fresh
        file_age = time.time() - os.path.getmtime(cache_file)
        if file_age > self.ttl:
            return None  # Cache expired

        with open(cache_file) as f:
            return json.load(f)

    def set(self, business_id, reviews):
        """Cache reviews."""
        cache_file = self._cache_key(business_id)

        with open(cache_file, 'w') as f:
            json.dump(reviews, f)

    def get_or_fetch(self, business_id):
        """Get from cache or fetch from API."""
        # Try cache first
        cached = self.get(business_id)
        if cached:
            print(f"Cache hit: {business_id}")
            return cached

        # Fetch from API
        print(f"Cache miss: {business_id}, fetching from API")
        reviews = managed_paginator.fetch_all(
            "reviews",
            params={"business_id": business_id}
        )

        # Store in cache
        self.set(business_id, reviews)
        return reviews

# Usage
cache = ReviewCache(ttl_seconds=3600)  # 1-hour cache

# First call: fetches from API (costs 100 credits)
reviews_1 = cache.get_or_fetch("12345")

# Second call (within 1 hour): serves from cache (costs 0 credits)
reviews_2 = cache.get_or_fetch("12345")

print(f"Second call saved 100 credits!")
```

**Savings Calculation**

**Assumptions:**

* Fetch 100 reviews per business = 100 credits
* Average 50% cache hit rate
* 100 requests/month

```
Without caching: 100 requests × 100 credits = 10,000 credits/month
With caching (50% hits): 50 requests × 100 credits = 5,000 credits/month
Savings: 50% (5,000 credits saved)
```

**When to use this strategy:**

* Repeated requests for the same business
* Data that doesn't change frequently
* User dashboards that refresh multiple times daily
* Batch jobs that re-query the same data

**Choose TTL based on data freshness:**

```python
# Real-time data: 5-minute cache
cache = ReviewCache(ttl_seconds=300)

# Hourly dashboard: 1-hour cache
cache = ReviewCache(ttl_seconds=3600)

# Daily batch: 24-hour cache
cache = ReviewCache(ttl_seconds=86400)

# Monthly reports: 7-day cache
cache = ReviewCache(ttl_seconds=604800)
```

***

#### Strategy 3: Batch Related Requests

**Impact: 30-60% credit savings**

Combine multiple related queries into fewer API calls using Managed pagination and smart filtering.

**Problem: Multiple Separate Requests**

```python
# BAD: 3 separate requests to fetch reviews for 3 businesses
business_ids = ["12345", "12346", "12347"]

for business_id in business_ids:
    # Each call is separate (no batch)
    reviews = managed_paginator.fetch_all(
        "reviews",
        params={"business_id": business_id}
    )
    # 100 credits × 3 = 300 credits total
```

**Solution: Batch with Filtering**

```python
# GOOD: Single request with batch processing
# Fetch reviews for all businesses, filter client-side
all_reviews = managed_paginator.fetch_all(
    "reviews",
    params={
        # Batch parameter: fetch for multiple IDs
        "business_ids": ["12345", "12346", "12347"],
        # Limit to recent reviews only
        "created_after": "2024-01-01"
    }
)

# Process by business
by_business = {}
for review in all_reviews:
    business_id = review['business_id']
    if business_id not in by_business:
        by_business[business_id] = []
    by_business[business_id].append(review)

# ~200 credits (vs 300 with 3 separate requests)
# Savings: 33%
```

**Batch + Cache Pattern**

```python
class BatchedReviewFetcher:
    def __init__(self, paginator, cache, batch_size=10):
        self.paginator = paginator
        self.cache = cache
        self.batch_size = batch_size

    def fetch_reviews_batch(self, business_ids):
        """Fetch reviews for multiple businesses in batches."""

        all_results = {}

        for i in range(0, len(business_ids), self.batch_size):
            batch = business_ids[i:i+self.batch_size]

            # Try cache first
            cached_batch = []
            to_fetch = []

            for bid in batch:
                cached = self.cache.get(bid)
                if cached:
                    all_results[bid] = cached
                else:
                    to_fetch.append(bid)

            # Fetch uncached items in batch
            if to_fetch:
                batch_reviews = self.paginator.fetch_all(
                    "reviews",
                    params={"business_ids": to_fetch}
                )

                # Separate by business and cache
                for review in batch_reviews:
                    bid = review['business_id']
                    if bid not in all_results:
                        all_results[bid] = []
                    all_results[bid].append(review)

                    # Cache individual business results
                    self.cache.set(bid, all_results[bid])

        return all_results

# Usage
fetcher = BatchedReviewFetcher(managed_paginator, cache, batch_size=10)

business_ids = ["12345", "12346", "12347", "12348", "12349"]
reviews_by_business = fetcher.fetch_reviews_batch(business_ids)

# Only fetch what's not cached
# If 3 of 5 are cached: 2 API calls instead of 5
# Savings: 60%
```

***

#### Strategy 4: Avoid Duplicate Requests

**Impact: 20-50% credit savings**

Deduplicate requests using a centralized service class.

**Problem: Duplicate Requests**

```python
# BAD: Multiple parts of code fetch the same data
class DashboardService:
    def get_reviews(self, business_id):
        return managed_paginator.fetch_all("reviews", {"business_id": business_id})

class ReportService:
    def generate_report(self, business_id):
        # Same request again!
        reviews = managed_paginator.fetch_all("reviews", {"business_id": business_id})

class AlertService:
    def check_alerts(self, business_id):
        # Same request AGAIN!
        reviews = managed_paginator.fetch_all("reviews", {"business_id": business_id})

# Same business data fetched 3 times (300 credits)
```

**Solution: Centralized Service with Caching**

```python
class ReviewService:
    """Centralized service for all review operations."""

    def __init__(self, paginator, cache_ttl=3600):
        self.paginator = paginator
        self.cache = ReviewCache(ttl_seconds=cache_ttl)

    def get_reviews(self, business_id, limit=None):
        """Get reviews for a business (cached)."""
        cached = self.cache.get(business_id)
        if cached:
            reviews = cached
        else:
            reviews = self.paginator.fetch_all(
                "reviews",
                params={"business_id": business_id}
            )
            self.cache.set(business_id, reviews)

        if limit:
            return reviews[:limit]
        return reviews

# Usage: Single service instance used everywhere
review_service = ReviewService(managed_paginator)

class DashboardService:
    def get_reviews(self, business_id):
        return review_service.get_reviews(business_id, limit=50)

class ReportService:
    def generate_report(self, business_id):
        return review_service.get_reviews(business_id)

class AlertService:
    def check_alerts(self, business_id):
        return review_service.get_reviews(business_id)

# Same data requested 3 times, fetched only once (100 credits)
# Savings: 67% (200 of 300 credits saved)
```

**Detect Duplicate Requests**

```python
import logging

class TrackedReviewService:
    def __init__(self, paginator):
        self.paginator = paginator
        self.cache = ReviewCache()
        self.request_count = {}
        self.logger = logging.getLogger(__name__)

    def get_reviews(self, business_id):
        """Track duplicate requests."""

        # Count requests
        self.request_count[business_id] = self.request_count.get(business_id, 0) + 1

        # Warn on duplicates
        if self.request_count[business_id] > 1:
            self.logger.warning(
                f"Duplicate request for business {business_id} "
                f"(call #{self.request_count[business_id]})"
            )

        return self.cache.get_or_fetch(business_id)

    def print_stats(self):
        """Print request statistics."""
        duplicates = sum(1 for c in self.request_count.values() if c > 1)
        total_requests = sum(self.request_count.values())
        potential_savings = sum(c - 1 for c in self.request_count.values()) * 100

        print(f"Total requests: {total_requests}")
        print(f"Unique businesses: {len(self.request_count)}")
        print(f"Duplicates: {duplicates}")
        print(f"Potential credit savings: {potential_savings} credits")

# Usage
service = TrackedReviewService(managed_paginator)
service.get_reviews("12345")
service.get_reviews("12345")  # Duplicate!
service.get_reviews("12346")

service.print_stats()
# Output:
# Total requests: 3
# Unique businesses: 2
# Duplicates: 1
# Potential credit savings: 100 credits
```

***

#### Strategy 5: Schedule Smart (Match Frequency to Data Change Rate)

**Impact: 40-80% credit savings**

Fetch data only as frequently as it changes. Over-fetching static data wastes credits.

**Fetch Frequency Strategy**

```python
import schedule
import time
from datetime import datetime

class SmartScheduler:
    def __init__(self, review_service):
        self.service = review_service

    def schedule_optimized(self):
        """Schedule fetches based on data change rate."""

        # Reviews change frequently: check multiple times/day
        schedule.every(2).hours.do(
            self.fetch_and_cache,
            endpoint="reviews",
            business_id="12345",
            reason="Reviews updated frequently"
        )

        # Search results change often: check multiple times/day
        schedule.every(6).hours.do(
            self.fetch_and_cache,
            endpoint="search",
            business_id="12345",
            reason="Search index updates"
        )

        # Business info changes rarely: check daily
        schedule.every().day.at("02:00").do(
            self.fetch_and_cache,
            endpoint="business_info",
            business_id="12345",
            reason="Business info updated daily"
        )

        # Photos rarely change: check weekly
        schedule.every().monday.at("03:00").do(
            self.fetch_and_cache,
            endpoint="photos",
            business_id="12345",
            reason="Photos updated weekly"
        )

        # Run scheduler
        while True:
            schedule.run_pending()
            time.sleep(60)

    def fetch_and_cache(self, endpoint, business_id, reason):
        """Fetch data and cache it."""
        print(f"[{datetime.now()}] Fetching {endpoint} - {reason}")
        self.service.fetch_and_cache(endpoint, business_id)

# Usage
scheduler = SmartScheduler(review_service)
scheduler.schedule_optimized()
```

**Frequency Recommendations by Data Type**

| Data Type      | Change Frequency | Recommended Check | Monthly Requests | Savings vs Every Hour |
| -------------- | ---------------- | ----------------- | ---------------- | --------------------- |
| Reviews        | Every few hours  | Every 2-3 hours   | 240              | 84%                   |
| Search results | Every 6-12 hours | Every 6 hours     | 120              | 92%                   |
| Business info  | Daily            | Once daily        | 30               | 98%                   |
| Photos         | Weekly           | Once weekly       | 4                | 99%                   |
| Categories     | Monthly          | Once monthly      | 1                | 99.7%                 |

**Example: Optimization Impact**

```
Reviews (change every 2 hours):
  - Bad: Check every hour = 24 × 30 = 720 requests/month
  - Good: Check every 2 hours = 12 × 30 = 360 requests/month
  - Savings: 50%

Business info (changes daily):
  - Bad: Check every 6 hours = 4 × 30 = 120 requests/month
  - Good: Check once daily = 1 × 30 = 30 requests/month
  - Savings: 75%

Combined monthly impact:
  - Bad: 840 requests = 840 credits
  - Good: 390 requests = 390 credits
  - Savings: 54%
```

***

#### Strategy 6: Use Test Keys for Development

**Impact: 100% savings on dev costs**

Never use production credits for development. Use test keys to prototype without cost.

**Setting Up Test Keys**

```python
import os
from dotenv import load_dotenv

load_dotenv()

# Production key: Real credits consumed
PROD_API_KEY = os.getenv("PULLBAY_API_KEY_PROD")

# Test key: No credits consumed, limited data
TEST_API_KEY = os.getenv("PULLBAY_API_KEY_TEST")

def get_client(use_production=False):
    """Get Pullbay client with appropriate key."""
    api_key = PROD_API_KEY if use_production else TEST_API_KEY

    return PullbayClient(api_key=api_key)

# Development
dev_client = get_client(use_production=False)

# Production
prod_client = get_client(use_production=True)
```

**Test Key Best Practices**

```python
class DevelopmentPullbay:
    """Use only in development/testing."""

    def __init__(self):
        self.client = PullbayClient(api_key=os.getenv("PULLBAY_API_KEY_TEST"))
        # Test keys have limits
        self.max_requests_per_hour = 100
        self.max_items_per_request = 50

    def fetch_reviews(self, business_id, limit=10):
        """Fetch limited reviews for testing."""
        return self.client.fetch_all(
            "reviews",
            params={
                "business_id": business_id,
                "max_results": limit  # Limit to 10 for fast testing
            }
        )

    def test_pagination(self, business_id):
        """Test pagination logic without high costs."""
        reviews = []
        cursor = None

        while len(reviews) < 50:  # Stop at 50 for testing
            page = self.client.paginate(
                "reviews",
                params={"business_id": business_id},
                cursor=cursor,
                limit=10
            )

            reviews.extend(page['data'])
            cursor = page['pagination'].get('next_cursor')

            if not cursor:
                break

        return reviews

# Usage
dev_client = DevelopmentPullbay()

# Fast iteration without cost
dev_reviews = dev_client.fetch_reviews("12345", limit=10)
print(f"Development: Fetched {len(dev_reviews)} reviews (0 credits used)")
```

**Cost Comparison**

```
Development with production key:
  - 10 days of testing
  - 100 requests/day, 100 items/request
  - Cost: 100,000 credits consumed

Development with test key:
  - Same work, same testing
  - Cost: 0 production credits consumed

Savings: 100,000 credits per development sprint
```

***

#### Strategy 7: Monitor and Alert (Prevent Runaway Spending)

**Impact: Prevents 100-1,000+ credit waste from bugs**

Set up monitoring to catch unusual usage patterns before they drain your account.

**Basic Alert System**

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

class CreditMonitor:
    def __init__(self, client, alert_thresholds=None):
        self.client = client
        self.thresholds = alert_thresholds or {
            'percent_50': 50,      # Alert at 50% of monthly budget
            'percent_75': 75,      # Critical at 75%
            'daily_spike': 2.0     # Alert if 2× higher than daily average
        }
        self.daily_history = []

    def check_and_alert(self):
        """Check usage and send alerts if needed."""
        usage = self.client.get_usage()

        # Alert 1: Percent of monthly budget
        percent_used = (usage['credits_used'] / usage['monthly_budget']) * 100

        if percent_used >= self.thresholds['percent_75']:
            self._send_alert(
                f"CRITICAL: {percent_used:.1f}% of monthly budget used",
                severity='critical'
            )
        elif percent_used >= self.thresholds['percent_50']:
            self._send_alert(
                f"WARNING: {percent_used:.1f}% of monthly budget used",
                severity='warning'
            )

        # Alert 2: Daily spending spike
        today_usage = usage['credits_today']
        self.daily_history.append((datetime.now(), today_usage))

        if len(self.daily_history) >= 7:
            avg_daily = sum(c for _, c in self.daily_history[-7:]) / 7
            spike_multiplier = today_usage / max(avg_daily, 1)

            if spike_multiplier >= self.thresholds['daily_spike']:
                self._send_alert(
                    f"Alert: Today's usage ({today_usage} credits) is "
                    f"{spike_multiplier:.1f}x daily average ({avg_daily:.0f})",
                    severity='warning'
                )

    def _send_alert(self, message, severity='info'):
        """Send alert via email/Slack/etc."""
        print(f"[{severity.upper()}] {message}")

        # TODO: Integrate with email/Slack/PagerDuty
        # send_email(alert_recipients, subject=f"Pullbay {severity}", body=message)
        # post_slack(message, channel=alerts_channel)

# Usage
monitor = CreditMonitor(
    client=pullbay_client,
    alert_thresholds={
        'percent_50': 50,
        'percent_75': 75,
        'daily_spike': 2.0
    }
)

# Run periodically (every hour)
schedule.every().hour.do(monitor.check_and_alert)
```

**Production Monitoring Checklist**

```python
class ProductionMonitoring:
    """Comprehensive monitoring for production."""

    @staticmethod
    def setup_alerts():
        """Set up all recommended alerts."""

        alerts = [
            {
                'name': '50% budget alert',
                'condition': 'credits_used > (monthly_budget * 0.5)',
                'recipients': ['ops@company.com']
            },
            {
                'name': '75% budget alert',
                'condition': 'credits_used > (monthly_budget * 0.75)',
                'recipients': ['ops@company.com', 'cto@company.com']
            },
            {
                'name': '2x daily spike',
                'condition': 'credits_today > (avg_daily_credits * 2)',
                'recipients': ['ops@company.com']
            },
            {
                'name': 'API errors spike',
                'condition': 'error_rate_1h > 5%',
                'recipients': ['ops@company.com']
            },
            {
                'name': 'Rate limit hits',
                'condition': 'rate_limit_hits_1h > 10',
                'recipients': ['ops@company.com']
            }
        ]

        for alert in alerts:
            print(f"Configured alert: {alert['name']}")
            print(f"  Recipients: {alert['recipients']}")

    @staticmethod
    def log_metrics():
        """Log detailed metrics."""
        metrics = {
            'timestamp': datetime.now().isoformat(),
            'credits_used_today': 0,
            'requests_made_today': 0,
            'avg_credits_per_request': 0,
            'top_endpoints': [],
            'error_rate': 0,
        }

        # Log to monitoring system (DataDog, New Relic, etc.)
        print(f"Metrics: {metrics}")
```

***

### Cost Estimation Formula

Estimate your monthly credit usage with this formula:

```
Monthly Credits = (Requests Per Endpoint) × (Pages Per Request) × (Frequency Per Month)

Example 1: Single Business, All Reviews
  Requests = 1
  Pages = 40 pages (at 50 items/page = 2,000 reviews)
  Frequency = 30 (once daily)
  Monthly Credits = 1 × 40 × 30 = 1,200 credits

Example 2: Multiple Apps (10 apps), Daily Reviews
  Reviews: 10 apps × 40 pages × 30 days = 12,000 credits/month

Example 3: With Optimization (Same Use Case, With Caching)
  Reviews (50% cache hits): 12,000 × 0.5 = 6,000 credits/month
  Savings: 6,000 credits/month (50%)
```

***

### Cost Estimation Spreadsheet

Create a spreadsheet to track and estimate costs:

```
| Endpoint | Freq | Pages | Requests/Mo | Credits/Request | Monthly Credits | Notes |
|----------|------|-------|-------------|-----------------|-----------------|-------|
| Reviews | Daily | 40 | 300 | 40 | 12,000 | 10 apps |
| Reviews (cached) | Daily | 40 | 150 | 40 | 6,000 | 50% cache hit |
| TOTAL (no cache) | | | | | 12,000 | |
| TOTAL (with cache) | | | | | 6,000 | 50% saving |
```

***

### When to Upgrade vs When to Optimize

#### Upgrade Plan if:

* Legitimate high usage (not bugs or inefficiency)
* Usage expected to grow significantly
* Current plan is limiting features (rate limits)
* Cost per credit becomes favorable at higher volumes

**Example: Should You Upgrade?**

```
Current usage: 50,000 credits/month — consistently hitting rate limits.

Options:
1. Optimize usage — reduce waste, implement caching, limit over-fetching
2. Upgrade plan — higher rate limits, more monthly credits included

If hitting rate limits regularly: upgrade for higher req/min limits.
If over-fetching data: optimize first before upgrading.
```

See the [Plans guide](broken://pages/4201be0f307231c3046d46d2ed7a069c7f0275d0) for specific plan details and pricing.

#### Optimize if:

* You have wasteful patterns (duplicates, over-fetching, infrequent changes fetched hourly)
* Current usage seems excessive for your use case
* Easy optimizations available (caching, limiting)

***

### Build vs Buy: Pullbay vs Custom Scrapers

Building and maintaining your own scrapers involves:

* Significant development time upfront
* Ongoing maintenance as source websites change
* Proxy infrastructure and anti-bot handling
* Monitoring for failures and schema changes

Pullbay handles all of this automatically. Your engineering time is spent on using the data, not maintaining the pipeline.

When evaluating total cost, factor in: developer hours for initial build, ongoing maintenance hours per month, infrastructure costs (proxies, servers), and the reliability risk when sites change structure.

***

### FAQ

<details>

<summary>What's the cheapest way to test the API?</summary>

**Use test keys with limited data:**

```python
# Test key (no credits used)
test_client = PullbayClient(api_key=os.getenv("PULLBAY_API_KEY_TEST"))

# Fetch limited sample
sample = test_client.fetch_all(
    "reviews",
    params={
        "business_id": "12345",
        "max_results": 10  # Small sample for testing
    }
)

print(f"Got {len(sample)} reviews, cost: 0 credits")
```

Test keys include:

* 100 requests/hour limit
* 50 items/request soft limit
* Simulated data similar to production
* Zero credit cost

</details>

<details>

<summary>How do I estimate my monthly credit usage?</summary>

**Use the formula:**

```
Monthly Credits = Requests × Pages × Frequency

Examples:
- One business, daily: 1 × 40 × 30 = 1,200 credits
- Ten businesses, hourly: 10 × 5 × 24 × 30 = 36,000 credits
- Five endpoints, daily: (5 × 20 pages × 30) = 3,000 credits
```

Or create a test run:

```python
import time

start_credits = client.get_account()['credits']
start_time = time.time()

# Run your typical workload
perform_typical_usage()

elapsed = time.time() - start_time
credits_used = start_credits - client.get_account()['credits']

daily_requests = requests_made / (elapsed / 86400)
monthly_estimate = (credits_used / requests_made) * (daily_requests * 30)

print(f"Estimated monthly usage: {monthly_estimate:.0f} credits")
```

</details>

<details>

<summary>Can I set a credit spending limit?</summary>

**Most plans support spending limits. Check your dashboard:**

1. Go to **Settings > Billing**
2. Set **Monthly Spending Limit**
3. Receive alerts when approaching limit
4. Automatic cutoff when limit reached

**Programmatic alert instead:**

```python
def check_budget(client, monthly_budget=5000, current_usage=None):
    if current_usage is None:
        current_usage = client.get_usage()['credits_used']

    percent_used = (current_usage / monthly_budget) * 100

    if percent_used >= 100:
        raise Exception("Monthly budget exceeded!")
    elif percent_used >= 75:
        send_alert(f"WARNING: {percent_used:.0f}% of budget used")

    return percent_used

# Usage
percent = check_budget(client, monthly_budget=5000)
print(f"Budget used: {percent:.0f}%")
```

</details>

***

### Summary

**Implement these strategies in order of impact:**

1. **Fetch only what you need** (50-90% savings) - Highest ROI
2. **Cache responses** (30-70% savings) - Quick to implement
3. **Batch related requests** (30-60% savings) - Plan ahead
4. **Avoid duplicates** (20-50% savings) - Refactor for clarity
5. **Schedule smart** (40-80% savings) - Match frequency to data changes
6. **Use test keys** (100% dev savings) - Never use production credits
7. **Monitor and alert** (Prevents waste) - Catch bugs early

**Combined impact:** 50-90% monthly credit reduction while improving performance and code quality.

Start with strategies 1 and 2 for immediate impact. Add the others as your usage scales.


---

# 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/platform-best-practices/cost-optimization-revisit.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.
