> 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/integrations/api-integration-guide.md).

# API Integration Guide

This guide covers integrating Pullbay's data APIs into backend applications and services. Learn authentication, error handling, rate limiting, and how to build robust integrations across Python, Node.js, PHP, and Ruby.

## Architecture: Server-Side Integration Only

**Critical Security Rule**: Never expose your Pullbay API key in client-side code.

### Why Server-Side Only?

API keys are secrets. Exposing them in client-side JavaScript, mobile apps, or public repositories allows attackers to:

* Consume your API quota and credits
* Impersonate your application
* Access your data
* Cause financial damage through runaway API usage

### Where to Store Your API Key

**Do:**

* ✅ Environment variables in your backend application
* ✅ Secrets manager (AWS Secrets Manager, HashiCorp Vault, Google Secret Manager)
* ✅ Encrypted configuration files (accessed only by your backend)
* ✅ Secure CI/CD pipeline secrets

**Don't:**

* ❌ Hardcoded in source files
* ❌ Client-side JavaScript or React
* ❌ Mobile app code
* ❌ Public repositories (GitHub, GitLab, etc.)
* ❌ Unencrypted configuration files

***

## Client-Side Access: Build a Backend Proxy

If your frontend needs to fetch Pullbay data, **never call Pullbay directly from the browser**. Instead, build a backend API endpoint that acts as a proxy:

```
Frontend → Your Backend Proxy → Pullbay API
```

### Proxy Pattern

```
GET /api/reviews?app_id=123
    ↓
[Your Backend Server]
    ↓ (API Key added securely)
Pullbay API
    ↓
[Your Backend Server]
    ↓
Frontend (JSON response)
```

**Benefits:**

* API key never leaves your backend
* Control who can access review data
* Add authentication/authorization
* Rate limit per user
* Cache responses to reduce credits

**Example Backend Proxy** (Node.js):

```javascript
app.get('/api/reviews', authMiddleware, async (req, res) => {
  const { app_id } = req.query;

  // Call Pullbay with API key from environment
  const response = await axios.get('https://api.pullbay.com/v1/app-store/reviews/all', {
    params: { app_id },
    headers: { 'Authorization': `Bearer ${process.env.PULLBAY_API_KEY}` }
  });

  // Return to frontend
  res.json(response.data);
});
```

***

## Centralized PullbayClient Class

Create a centralized client class for all Pullbay API calls. This ensures consistent authentication, error handling, and credit management across your application.

### Python Example

```python
import os
import requests
from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter

class PullbayClient:
    """
    Centralized client for Pullbay API calls.
    Handles authentication, retries, error handling, and credit tracking.
    """

    def __init__(self, api_key=None):
        self.api_key = api_key or os.getenv('PULLBAY_API_KEY')
        if not self.api_key:
            raise ValueError('PULLBAY_API_KEY environment variable not set')

        self.base_url = 'https://api.pullbay.com/v1'
        self.headers = {
            'Authorization': f'Bearer {self.api_key}',
            'Content-Type': 'application/json'
        }

        # Session with retry logic
        self.session = requests.Session()
        retry_strategy = Retry(
            total=3,
            status_forcelist=[429, 500, 502, 503, 504],
            method_whitelist=['GET'],
            backoff_factor=1
        )
        adapter = HTTPAdapter(max_retries=retry_strategy)
        self.session.mount('http://', adapter)
        self.session.mount('https://', adapter)

    def get_app_store_reviews(self, app_id, country='us', sort='recent', max_results=None):
        """
        Fetch all reviews for an app using managed pagination.

        Args:
            app_id (int): The numeric App Store ID
            country (str): Two-letter country code (default: 'us')
            sort (str): Sort order - 'recent', 'helpful', or 'critical'
            max_results (int): Maximum results to return (None = all)

        Returns:
            dict: Response with 'data' and 'meta' fields

        Raises:
            requests.HTTPError: On API errors
        """
        params = {
            'app_id': app_id,
            'country': country,
            'sort': sort
        }

        if max_results:
            params['max_results'] = max_results

        response = self.session.get(
            f'{self.base_url}/app-store/reviews/all',
            headers=self.headers,
            params=params,
            timeout=120
        )

        response.raise_for_status()
        return response.json()

# Usage
client = PullbayClient()
reviews = client.get_app_store_reviews(app_id=284882215, country='us')
print(f"Fetched {len(reviews['data'])} reviews")
```

***

## Implementation Checklist

Before deploying your integration, ensure you've implemented:

* [ ] **Authentication**: API key stored in environment variables or secrets manager
* [ ] **Error Handling**: Catch and handle 4xx and 5xx errors appropriately
* [ ] **Rate Limiting**: Respect rate limits; implement exponential backoff
* [ ] **Credit Management**: Track credit usage and implement alerts
* [ ] **Logging**: Log all API requests with request IDs for debugging
* [ ] **Retries**: Implement retry logic for transient failures (5xx errors)
* [ ] **Timeout**: Set appropriate timeout for API requests (120 seconds minimum for managed endpoints)
* [ ] **Monitoring**: Set up monitoring and alerts for API failures
* [ ] **Circuit Breaker**: Implement circuit breaker pattern to prevent cascading failures
* [ ] **Testing**: Use test API keys and mock responses in unit tests

***

## Language-Specific Examples

{% tabs %}
{% tab title="Python with Requests and Retries" %}
Complete production-ready Python integration:

```python
import os
import logging
import requests
from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter
from datetime import datetime, timedelta

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class PullbayClient:
    """Production-grade Pullbay API client with full error handling."""

    def __init__(self, api_key=None, max_retries=3):
        self.api_key = api_key or os.getenv('PULLBAY_API_KEY')
        if not self.api_key:
            raise ValueError('PULLBAY_API_KEY not configured')

        self.base_url = 'https://api.pullbay.com/v1'
        self.max_retries = max_retries
        self.credits_used = 0
        self.last_rate_limit_reset = None
        self.requests_this_minute = 0

        self.session = self._build_session()

    def _build_session(self):
        """Build a requests session with retry logic."""
        session = requests.Session()

        # Configure retries for transient failures
        retry_strategy = Retry(
            total=self.max_retries,
            status_forcelist=[429, 500, 502, 503, 504],
            method_whitelist=['GET'],
            backoff_factor=1,  # 1s, 2s, 4s for retries
            raise_on_status=False
        )

        adapter = HTTPAdapter(max_retries=retry_strategy)
        session.mount('http://', adapter)
        session.mount('https://', adapter)

        return session

    def get_app_store_reviews(self, app_id, country='us', sort='recent', max_results=None):
        """
        Fetch app reviews from the App Store.

        Args:
            app_id (int): App Store ID
            country (str): Country code (default: 'us')
            sort (str): 'recent', 'helpful', or 'critical'
            max_results (int): Maximum results (None = all)

        Returns:
            dict: API response with data and metadata
        """
        # Rate limiting: Max 10 requests per minute
        self._check_rate_limit()

        params = {
            'app_id': app_id,
            'country': country,
            'sort': sort
        }

        if max_results:
            params['max_results'] = max_results

        try:
            logger.info(f"Fetching reviews for app {app_id} from {country}")

            response = self.session.get(
                f'{self.base_url}/app-store/reviews/all',
                headers={
                    'Authorization': f'Bearer {self.api_key}',
                    'Content-Type': 'application/json'
                },
                params=params,
                timeout=120  # 2 minutes for managed pagination
            )

            # Handle HTTP errors
            if response.status_code == 401:
                logger.error('Authentication failed - invalid API key')
                raise ValueError('Invalid API key')

            if response.status_code == 429:
                logger.warning('Rate limited - backing off')
                raise RuntimeError('Rate limited by Pullbay API')

            if response.status_code >= 500:
                logger.error(f'Server error: {response.status_code}')
                raise RuntimeError(f'Pullbay API error: {response.status_code}')

            response.raise_for_status()

            # Track credits
            data = response.json()
            credits_used = data.get('meta', {}).get('credits_used', 1)
            self.credits_used += credits_used
            self.requests_this_minute += 1

            logger.info(f"Successfully fetched {len(data['data'])} reviews ({credits_used} credits)")

            return data

        except requests.exceptions.Timeout:
            logger.error('Request timeout - Pullbay API not responding')
            raise
        except requests.exceptions.ConnectionError:
            logger.error('Connection error - unable to reach Pullbay API')
            raise
        except Exception as e:
            logger.error(f'Unexpected error: {str(e)}')
            raise

    def _check_rate_limit(self):
        """Enforce rate limiting (10 requests per minute)."""
        now = datetime.utcnow()

        if self.last_rate_limit_reset is None:
            self.last_rate_limit_reset = now
            self.requests_this_minute = 0

        # Reset counter every minute
        if (now - self.last_rate_limit_reset).total_seconds() > 60:
            self.last_rate_limit_reset = now
            self.requests_this_minute = 0

        # Check limit (adjust as needed for your plan)
        if self.requests_this_minute >= 10:
            wait_time = 60 - (now - self.last_rate_limit_reset).total_seconds()
            raise RuntimeError(f'Rate limit exceeded. Wait {wait_time:.0f} seconds.')

# Usage Example
if __name__ == '__main__':
    client = PullbayClient()

    try:
        reviews = client.get_app_store_reviews(
            app_id=284882215,
            country='us',
            sort='recent',
            max_results=100
        )

        print(f"Total reviews: {len(reviews['data'])}")
        print(f"Pages fetched: {reviews['meta']['pages_fetched']}")
        print(f"Credits used: {reviews['meta']['credits_used']}")

        # Process reviews
        for review in reviews['data']:
            print(f"\n{review['author']} ({review['rating']}⭐)")
            print(f"  {review['title']}")
            print(f"  {review['content'][:100]}...")

    except Exception as e:
        print(f"Error: {e}")
```

{% endtab %}

{% tab title="Node.js with Axios and Async/Await" %}
Complete Node.js integration:

```javascript
const axios = require('axios');

class PullbayClient {
  constructor(apiKey = null) {
    this.apiKey = apiKey || process.env.PULLBAY_API_KEY;
    if (!this.apiKey) {
      throw new Error('PULLBAY_API_KEY environment variable not set');
    }

    this.baseURL = 'https://api.pullbay.com/v1';
    this.creditsUsed = 0;
    this.client = axios.create({
      baseURL: this.baseURL,
      timeout: 120000, // 2 minutes
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json'
      }
    });

    // Add retry interceptor
    this.client.interceptors.response.use(null, this._axiosRetryInterceptor.bind(this));
  }

  async getAppStoreReviews(appId, options = {}) {
    /**
     * Fetch reviews for an app.
     *
     * @param {number} appId - App Store ID
     * @param {Object} options - Query options
     * @param {string} options.country - Country code (default: 'us')
     * @param {string} options.sort - 'recent', 'helpful', or 'critical'
     * @param {number} options.maxResults - Max results to return
     * @returns {Promise<Object>} API response
     */

    const params = {
      app_id: appId,
      country: options.country || 'us',
      sort: options.sort || 'recent'
    };

    if (options.maxResults) {
      params.max_results = options.maxResults;
    }

    try {
      console.log(`Fetching reviews for app ${appId}...`);

      const response = await this.client.get('/app-store/reviews/all', { params });

      const { data, meta } = response.data;
      this.creditsUsed += meta.credits_used;

      console.log(`Successfully fetched ${data.length} reviews (${meta.credits_used} credits)`);

      return response.data;
    } catch (error) {
      if (error.response?.status === 401) {
        throw new Error('Authentication failed - invalid API key');
      }
      if (error.response?.status === 429) {
        throw new Error('Rate limited - too many requests');
      }
      if (error.response?.status >= 500) {
        throw new Error(`Pullbay API error: ${error.response.status}`);
      }
      throw error;
    }
  }

  async _axiosRetryInterceptor(error) {
    /**
     * Retry logic for transient failures.
     */
    const { config } = error;

    if (!config) {
      return Promise.reject(error);
    }

    config.retryCount = config.retryCount || 0;

    // Only retry on 5xx errors and connection errors
    const isRetryable =
      (error.response?.status >= 500) ||
      error.code === 'ECONNABORTED' ||
      error.code === 'ECONNRESET';

    if (isRetryable && config.retryCount < 3) {
      config.retryCount += 1;

      // Exponential backoff: 1s, 2s, 4s
      const delay = Math.pow(2, config.retryCount - 1) * 1000;
      console.log(`Retrying request (attempt ${config.retryCount}) after ${delay}ms...`);

      await new Promise(resolve => setTimeout(resolve, delay));

      return this.client(config);
    }

    return Promise.reject(error);
  }
}

// Usage Example
(async () => {
  try {
    const client = new PullbayClient();

    const response = await client.getAppStoreReviews(284882215, {
      country: 'us',
      sort: 'recent',
      maxResults: 100
    });

    console.log(`\nTotal reviews: ${response.data.length}`);
    console.log(`Pages fetched: ${response.meta.pages_fetched}`);
    console.log(`Credits used: ${response.meta.credits_used}`);

    // Process reviews
    response.data.forEach(review => {
      console.log(`\n${review.author} (${review.rating}⭐)`);
      console.log(`  ${review.title}`);
      console.log(`  ${review.content.substring(0, 100)}...`);
    });
  } catch (error) {
    console.error(`Error: ${error.message}`);
  }
})();

module.exports = PullbayClient;
```

{% endtab %}

{% tab title="PHP with Guzzle HTTP Client" %}
Complete PHP integration:

```php
<?php

use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Psr\Http\Message\ResponseInterface;

class PullbayClient {
    private $httpClient;
    private $apiKey;
    private $creditsUsed = 0;

    public function __construct($apiKey = null) {
        $this->apiKey = $apiKey ?: getenv('PULLBAY_API_KEY');

        if (!$this->apiKey) {
            throw new Exception('PULLBAY_API_KEY environment variable not set');
        }

        // Create HTTP client with retry middleware
        $stack = HandlerStack::create();
        $stack->push(Middleware::retry($this->getRetryDecider(), $this->getRetryDelay()));

        $this->httpClient = new Client([
            'base_uri' => 'https://api.pullbay.com/v1',
            'handler' => $stack,
            'timeout' => 120,
            'headers' => [
                'Authorization' => 'Bearer ' . $this->apiKey,
                'Content-Type' => 'application/json'
            ]
        ]);
    }

    public function getAppStoreReviews($appId, $options = []) {
        /**
         * Fetch reviews for an app.
         *
         * @param int $appId App Store ID
         * @param array $options Query options
         * @return array API response
         */

        $query = [
            'app_id' => $appId,
            'country' => $options['country'] ?? 'us',
            'sort' => $options['sort'] ?? 'recent'
        ];

        if (isset($options['max_results'])) {
            $query['max_results'] = $options['max_results'];
        }

        try {
            echo "Fetching reviews for app {$appId}...\n";

            $response = $this->httpClient->get('/app-store/reviews/all', [
                'query' => $query
            ]);

            $data = json_decode($response->getBody(), true);
            $this->creditsUsed += $data['meta']['credits_used'];

            echo "Successfully fetched " . count($data['data']) . " reviews ({$data['meta']['credits_used']} credits)\n";

            return $data;
        } catch (\GuzzleHttp\Exception\ClientException $e) {
            if ($e->getResponse()->getStatusCode() === 401) {
                throw new Exception('Authentication failed - invalid API key');
            }
            throw new Exception('API Error: ' . $e->getMessage());
        }
    }

    private function getRetryDecider() {
        /**
         * Decide whether to retry based on response status.
         */
        return function($retries, $request, ResponseInterface $response = null, $error = null) {
            // Retry on 5xx errors, connection errors, and timeouts
            if ($response !== null && $response->getStatusCode() >= 500) {
                return true;
            }
            if ($error !== null && in_array($error->getCode(), [
                'ECONNRESET',
                'ECONNREFUSED',
                'ETIMEDOUT',
                'EHOSTUNREACH'
            ])) {
                return true;
            }
            return $retries < 3;
        };
    }

    private function getRetryDelay() {
        /**
         * Calculate exponential backoff delay.
         */
        return function($retries) {
            return 1000 * pow(2, $retries);
        };
    }

    public function getCreditsUsed() {
        return $this->creditsUsed;
    }
}

// Usage Example
try {
    $client = new PullbayClient();

    $response = $client->getAppStoreReviews(284882215, [
        'country' => 'us',
        'sort' => 'recent',
        'max_results' => 100
    ]);

    echo "\nTotal reviews: " . count($response['data']) . "\n";
    echo "Pages fetched: " . $response['meta']['pages_fetched'] . "\n";
    echo "Credits used: " . $response['meta']['credits_used'] . "\n";

    // Process reviews
    foreach ($response['data'] as $review) {
        echo "\n{$review['author']} ({$review['rating']}⭐)\n";
        echo "  {$review['title']}\n";
        echo "  " . substr($review['content'], 0, 100) . "...\n";
    }
} catch (Exception $e) {
    echo "Error: {$e->getMessage()}\n";
}

?>
```

{% endtab %}

{% tab title="Ruby with Net::HTTP" %}
Complete Ruby integration:

```ruby
require 'net/http'
require 'json'
require 'uri'

class PullbayClient
  def initialize(api_key = nil)
    @api_key = api_key || ENV['PULLBAY_API_KEY']
    raise 'PULLBAY_API_KEY not set' unless @api_key

    @base_url = 'https://api.pullbay.com/v1'
    @credits_used = 0
    @max_retries = 3
  end

  def get_app_store_reviews(app_id, options = {})
    """
    Fetch reviews for an app.

    Args:
      app_id (int): App Store ID
      options (Hash): Query options
        - country: Country code (default: 'us')
        - sort: 'recent', 'helpful', or 'critical'
        - max_results: Maximum results to return
    """

    params = {
      app_id: app_id,
      country: options[:country] || 'us',
      sort: options[:sort] || 'recent'
    }

    params[:max_results] = options[:max_results] if options[:max_results]

    puts "Fetching reviews for app #{app_id}..."

    response = make_request(
      method: :get,
      path: '/app-store/reviews/all',
      params: params
    )

    data = JSON.parse(response.body)
    @credits_used += data['meta']['credits_used']

    puts "Successfully fetched #{data['data'].length} reviews (#{data['meta']['credits_used']} credits)"

    data
  end

  private

  def make_request(method:, path:, params: {})
    """
    Make an HTTP request with retry logic.
    """

    retries = 0
    loop do
      begin
        uri = build_uri(path, params)
        http = Net::HTTP.new(uri.host, uri.port)
        http.use_ssl = true
        http.read_timeout = 120

        request = case method
                  when :get
                    Net::HTTP::Get.new(uri)
                  when :post
                    Net::HTTP::Post.new(uri)
                  end

        request['Authorization'] = "Bearer #{@api_key}"
        request['Content-Type'] = 'application/json'

        response = http.request(request)

        case response.code.to_i
        when 401
          raise 'Authentication failed - invalid API key'
        when 429
          raise 'Rate limited - too many requests'
        when 500..599
          raise "Server error: #{response.code}"
        when 200..299
          return response
        else
          raise "HTTP #{response.code}: #{response.body}"
        end

      rescue StandardError => e
        retries += 1
        if retries <= @max_retries && (e.message.include?('Server error') || e.message.include?('connection'))
          wait_time = 2 ** retries
          puts "Retry #{retries}/#{@max_retries} after #{wait_time}s: #{e.message}"
          sleep(wait_time)
        else
          raise e
        end
      end
    end
  end

  def build_uri(path, params)
    """
    Build URI with query parameters.
    """

    uri = URI("#{@base_url}#{path}")
    uri.query = URI.encode_www_form(params)
    uri
  end
end

# Usage Example
begin
  client = PullbayClient.new

  response = client.get_app_store_reviews(284882215, {
    country: 'us',
    sort: 'recent',
    max_results: 100
  })

  puts "\nTotal reviews: #{response['data'].length}"
  puts "Pages fetched: #{response['meta']['pages_fetched']}"
  puts "Credits used: #{response['meta']['credits_used']}"

  # Process reviews
  response['data'].each do |review|
    puts "\n#{review['author']} (#{review['rating']}⭐)"
    puts "  #{review['title']}"
    puts "  #{review['content'][0...100]}..."
  end
rescue StandardError => e
  puts "Error: #{e.message}"
end
```

{% endtab %}
{% endtabs %}

***

## Testing and Mocking

### Using Test API Keys

Pullbay provides test API keys for development and testing:

1. Request a test API key from your dashboard
2. Test keys are rate-limited but free (no credits charged)
3. Use test keys in development and staging environments

### Mocking Responses in Unit Tests

Never make real API calls in unit tests. Mock the responses instead.

#### Python Mock Example

```python
import unittest
from unittest.mock import patch, MagicMock
from my_app import PullbayClient

class TestPullbayIntegration(unittest.TestCase):

    @patch('my_app.PullbayClient.session')
    def test_get_app_store_reviews(self, mock_session):
        """Test review fetching with mocked response."""

        # Mock the API response
        mock_response = MagicMock()
        mock_response.json.return_value = {
            'data': [
                {
                    'id': 'review_123',
                    'author': 'test_user',
                    'rating': 5,
                    'title': 'Great app!',
                    'content': 'Loved this app',
                    'date': '2024-04-10T00:00:00Z',
                    'version': '1.0.0',
                    'helpful_count': 10
                }
            ],
            'meta': {
                'total_results': 1,
                'pages_fetched': 1,
                'credits_used': 1
            }
        }

        mock_session.get.return_value = mock_response

        # Test the client
        client = PullbayClient(api_key='test_key_123')
        result = client.get_app_store_reviews(app_id=284882215)

        # Assertions
        self.assertEqual(len(result['data']), 1)
        self.assertEqual(result['data'][0]['author'], 'test_user')
        self.assertEqual(result['meta']['credits_used'], 1)

if __name__ == '__main__':
    unittest.main()
```

***

## Security Considerations

### Environment Variables vs Secrets Manager

**For Development:** Use `.env` files with environment variables:

```bash
PULLBAY_API_KEY=sk_test_xxxxxxxxxxxx
PULLBAY_ENV=development
```

Load with:

```python
import os
api_key = os.getenv('PULLBAY_API_KEY')
```

**For Production:** Use a secrets manager:

* **AWS**: AWS Secrets Manager
* **Google Cloud**: Google Secret Manager
* **Kubernetes**: Kubernetes Secrets
* **General**: HashiCorp Vault

Example (AWS Secrets Manager):

```python
import boto3

def get_api_key():
    client = boto3.client('secretsmanager', region_name='us-east-1')
    response = client.get_secret_value(SecretId='pullbay-api-key')
    return response['SecretString']
```

### API Key Rotation

Implement regular API key rotation:

1. Generate new API key in Pullbay dashboard
2. Update secrets manager with new key
3. Redeploy application
4. Verify new key works
5. Deactivate old key

***

## FAQ

<details>

<summary>Should I cache Pullbay responses?</summary>

Yes, caching can reduce credits and improve performance:

```python
from functools import lru_cache
from datetime import datetime, timedelta

class CachedPullbayClient(PullbayClient):
    def __init__(self, *args, cache_ttl=3600, **kwargs):
        super().__init__(*args, **kwargs)
        self.cache = {}
        self.cache_ttl = cache_ttl  # seconds

    def get_app_store_reviews(self, app_id, **options):
        cache_key = f"{app_id}:{options.get('country', 'us')}"

        # Check cache
        if cache_key in self.cache:
            cached_data, timestamp = self.cache[cache_key]
            if datetime.now() - timestamp < timedelta(seconds=self.cache_ttl):
                return cached_data

        # Fetch fresh data
        data = super().get_app_store_reviews(app_id, **options)
        self.cache[cache_key] = (data, datetime.now())
        return data
```

</details>

<details>

<summary>How do I handle API key rotation in my app?</summary>

Store the API key in a secrets manager rather than the code. When you rotate the key:

1. Update the secrets manager
2. The application will use the new key on next request
3. No code changes or redeployment needed

</details>

<details>

<summary>Can I call Pullbay from a browser?</summary>

**No.** Never expose your API key in client-side code. Build a backend proxy instead (see "Client-Side Access" section above).

</details>

<details>

<summary>What's the best way to handle rate limiting?</summary>

Implement exponential backoff:

```python
import time

def call_with_backoff(func, *args, max_retries=3):
    for attempt in range(max_retries):
        try:
            return func(*args)
        except RateLimitError:
            if attempt == max_retries - 1:
                raise
            wait_time = 2 ** attempt
            time.sleep(wait_time)
```

</details>

<details>

<summary>How do I monitor API usage?</summary>

1. Track credits used in your application
2. Log all API calls with timestamps
3. Set up alerts when credit usage exceeds thresholds
4. Monitor response times for performance issues
5. Use your Pullbay dashboard to view usage analytics

</details>

***

## Next Steps

* [**App Store API Reference**](broken://pages/d1a7458aef47c466a5a3e40cf829f0231eaeac59): Complete endpoint documentation
* [**n8n Integration Guide**](broken://pages/ecc7f64d70a3f6035c521b5a75082a6aa9dc33bd): Automation workflows without code
* [**Services Overview**](broken://pages/e2acf9efeb5adb82589255aaadfadcaaffeddfb3): Explore all available data sources

For API support, visit your Pullbay dashboard or contact our support team.


---

# 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/integrations/api-integration-guide.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.
