Skip to content

Rate Limiting

Bagisto APIs implement rate limiting to ensure fair usage and protect infrastructure from abuse. This guide explains how rate limiting works and how to handle rate limit responses.

Overview

Rate limiting protects the API from excessive requests by restricting the number of API calls a client can make in a given time window. Each Storefront API Key has its own rate limit quota.

Default Limits:

  • Public APIs (Shop): 100 requests per minute per API key
  • Customer APIs: 100 requests per minute per API key
  • Admin APIs: Configurable (higher limits for admin operations)

Rate Limit Headers

Every API response includes rate limit information in the response headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1767967200
HeaderDescriptionTypeExample
X-RateLimit-LimitMaximum requests allowed in the current windowinteger100
X-RateLimit-RemainingNumber of requests remaining in current windowinteger95
X-RateLimit-ResetUnix timestamp when the rate limit window resetsinteger1767967200

Rate Limit Window

Rate limits are calculated on a per-minute basis:

  • The window resets every 60 seconds
  • X-RateLimit-Reset shows when the current window expires (Unix timestamp)
  • After the reset time, your request quota is refreshed

Example Timeline:

Time: 13:00:00 UTC
Requests made: 5
Remaining: 95
Reset at: 13:01:00 UTC (Unix: 1767967200)

Time: 13:00:30 UTC (30 seconds later)
Requests made: 15 (total: 20)
Remaining: 80
Reset at: 13:01:00 UTC (Unix: 1767967200)

Time: 13:01:00 UTC (reset)
Requests made: 0
Remaining: 100 (quota refreshed!)
Reset at: 13:02:00 UTC (Unix: 1767967200 + 60)

Handling Rate Limit Exceeded

When you exceed the rate limit, you'll receive a 429 Too Many Requests response:

http
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1767967200
Retry-After: 52

Response Body:

json
{
  "message": "Rate limit exceeded",
  "error": "rate_limit_exceeded",
  "retry_after": 52
}
FieldDescriptionExample
messageHuman-readable error messageRate limit exceeded
errorMachine-readable error coderate_limit_exceeded
retry_afterSeconds to wait before retrying52

Configuring Rate Limits

Per-API Key Configuration

Set custom rate limits when creating a Storefront API Key:

bash
# Create key with custom rate limit (requests per minute)
php artisan bagisto-api:generate-key --name="Mobile App" --rate-limit=500

# Output:
# ✓ API key generated successfully!
# Key: sk_live_xxxxxxxxxxxxx
# Name: Mobile App
# Rate Limit: 500 requests/minute
# Status: Active

Environment Configuration

Set the default rate limit in your .env file:

bash
# .env

# Default rate limit for all new API keys (requests per minute)
BAGISTO_API_RATE_LIMIT=100

# Or configure per API type:
BAGISTO_API_RATE_LIMIT_PUBLIC=100
BAGISTO_API_RATE_LIMIT_CUSTOMER=100
BAGISTO_API_RATE_LIMIT_ADMIN=500

Configuration File

Configure rate limiting in config/bagisto-api.php:

php
return [
    'rate_limit' => [
        // Default rate limit for new keys (requests per minute)
        'default' => env('BAGISTO_API_RATE_LIMIT', 100),
        
        // Per API type limits
        'shop' => env('BAGISTO_API_RATE_LIMIT_PUBLIC', 100),
        'customer' => env('BAGISTO_API_RATE_LIMIT_CUSTOMER', 100),
        'admin' => env('BAGISTO_API_RATE_LIMIT_ADMIN', 500),
        
        // Window duration (in seconds)
        'window' => 60,
        
        // Enable rate limiting
        'enabled' => true,
    ],
];

Checking Current Rate Limit

Use the rate limit headers to monitor your quota:

javascript
// JavaScript/Fetch
fetch('https://your-domain.com/api/shop/products', {
  headers: {
    'X-STOREFRONT-KEY': 'pk_storefront_xxxxx'
  }
})
.then(response => {
  const limit = response.headers.get('X-RateLimit-Limit');
  const remaining = response.headers.get('X-RateLimit-Remaining');
  const reset = response.headers.get('X-RateLimit-Reset');
  
  console.log(`Limit: ${limit}, Remaining: ${remaining}, Reset: ${reset}`);
  
  return response.json();
})
.catch(error => console.error(error));

Best Practices

1. Monitor Rate Limit Headers

Always check the X-RateLimit-Remaining header to know when you're approaching the limit:

javascript
async function makeRequest(url, options) {
  const response = await fetch(url, options);
  
  const limit = parseInt(response.headers.get('X-RateLimit-Limit'));
  const remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
  
  if (remaining < limit * 0.1) {
    console.warn(`Warning: Only ${remaining} requests remaining`);
  }
  
  return response.json();
}

2. Implement Exponential Backoff

When rate limited, use exponential backoff before retrying:

javascript
async function makeRequestWithRetry(url, options, maxRetries = 3) {
  let attempt = 0;
  
  while (attempt < maxRetries) {
    try {
      const response = await fetch(url, options);
      
      if (response.status === 429) {
        const retryAfter = parseInt(response.headers.get('Retry-After')) || 60;
        const delay = Math.pow(2, attempt) * retryAfter * 1000;
        
        console.log(`Rate limited. Retrying after ${delay}ms`);
        await new Promise(r => setTimeout(r, delay));
        attempt++;
        continue;
      }
      
      return response.json();
    } catch (error) {
      console.error('Request failed:', error);
      throw error;
    }
  }
  
  throw new Error('Max retries exceeded');
}

3. Batch Requests

Combine multiple operations into batch requests to reduce API calls:

javascript
// ❌ Bad: Multiple requests
for (let id of productIds) {
  const product = await fetch(`/api/shop/products/${id}`);
}

// ✅ Good: Batch request
const products = await fetch('/api/shop/products?ids=1,2,3,4,5');

4. Cache Responses

Cache API responses to reduce unnecessary requests:

javascript
const cache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes

async function cachedFetch(url) {
  const now = Date.now();
  const cached = cache.get(url);
  
  if (cached && now - cached.time < CACHE_TTL) {
    return cached.data;
  }
  
  const data = await fetch(url).then(r => r.json());
  cache.set(url, { data, time: now });
  
  return data;
}

5. Queue Requests

Spread requests over time to avoid burst traffic:

javascript
class RequestQueue {
  constructor(rateLimit = 10) {
    this.queue = [];
    this.processing = false;
    this.requestsPerSecond = rateLimit;
  }

  async add(request) {
    return new Promise((resolve, reject) => {
      this.queue.push({ request, resolve, reject });
      this.process();
    });
  }

  async process() {
    if (this.processing || this.queue.length === 0) return;
    
    this.processing = true;
    
    while (this.queue.length > 0) {
      const { request, resolve, reject } = this.queue.shift();
      
      try {
        const result = await request();
        resolve(result);
      } catch (error) {
        reject(error);
      }
      
      // Delay between requests
      await new Promise(r => setTimeout(r, 1000 / this.requestsPerSecond));
    }
    
    this.processing = false;
  }
}

// Usage
const queue = new RequestQueue(10); // 10 requests per second

queue.add(() => fetch('/api/shop/products/1'));
queue.add(() => fetch('/api/shop/products/2'));
queue.add(() => fetch('/api/shop/products/3'));

6. Handle Rate Limit Gracefully

Provide user feedback when rate limited:

javascript
async function makeRequestWithFallback(url, options) {
  try {
    const response = await fetch(url, options);
    
    if (response.status === 429) {
      const data = await response.json();
      const waitTime = data.retry_after;
      
      // Show user message
      console.error(`API temporarily unavailable. Please try again in ${waitTime} seconds`);
      
      // Retry after waiting
      await new Promise(r => setTimeout(r, waitTime * 1000));
      return makeRequestWithFallback(url, options);
    }
    
    return response.json();
  } catch (error) {
    console.error('Request failed:', error);
    throw error;
  }
}

Monitoring Rate Limit Usage

Track Usage Over Time

javascript
class RateLimitMonitor {
  constructor() {
    this.stats = {
      totalRequests: 0,
      rateLimitedRequests: 0,
      averageRemaining: 0
    };
  }

  trackResponse(response) {
    const remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
    const limit = parseInt(response.headers.get('X-RateLimit-Limit'));
    
    this.stats.totalRequests++;
    
    if (response.status === 429) {
      this.stats.rateLimitedRequests++;
    }
    
    this.stats.averageRemaining = 
      (this.stats.averageRemaining * (this.stats.totalRequests - 1) + remaining) / 
      this.stats.totalRequests;
    
    return {
      limitPercentage: Math.round((remaining / limit) * 100),
      status: remaining < limit * 0.2 ? 'warning' : 'ok'
    };
  }

  getReport() {
    const rateLimitPercentage = 
      Math.round((this.stats.rateLimitedRequests / this.stats.totalRequests) * 100);
    
    return {
      totalRequests: this.stats.totalRequests,
      rateLimitedCount: this.stats.rateLimitedRequests,
      rateLimitPercentage: rateLimitPercentage,
      averageRemaining: Math.round(this.stats.averageRemaining)
    };
  }
}

API Key Management

View Rate Limit Status

Check your API key's rate limit configuration:

bash
# Check rate limit for a specific key
php artisan bagisto-api:key:manage status --key="Mobile App"

# Output:
# Key:          Mobile App
# Status:       Active
# Rate Limit:   500 requests/minute
# Total Requests (last 24h): 12,450
# Average RPM:  345

Adjust Rate Limit

Update an API key's rate limit:

bash
# Increase rate limit for high-traffic app
php artisan bagisto-api:generate-key --name="High Traffic App" --rate-limit=2000

List All Keys with Rate Limits

bash
# Summary of all keys and their limits
php artisan bagisto-api:key:manage summary

# Output:
# Total Keys: 5
# 
# Key Name            | Status   | Rate Limit | Requests (24h)
# ------------------- | -------- | ---------- | ---------------
# Mobile App          | Active   | 500        | 12,450
# Website             | Active   | 200        | 5,230
# Partner API         | Active   | 1000       | 8,945
# Development         | Inactive | 100        | 0
# Testing             | Active   | 50         | 145

Examples

cURL

bash
# Check rate limit headers
curl -X GET 'https://your-domain.com/api/shop/products' \
  -H 'X-STOREFRONT-KEY: pk_storefront_xxxxx' \
  -i  # Include headers in output

Response:

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1767967200

Python

python
import requests
import time

def make_request_with_rate_limit(url, headers):
    response = requests.get(url, headers=headers)
    
    limit = int(response.headers.get('X-RateLimit-Limit', 100))
    remaining = int(response.headers.get('X-RateLimit-Remaining', 100))
    reset = int(response.headers.get('X-RateLimit-Reset', 0))
    
    print(f"Rate Limit: {remaining}/{limit}")
    
    if response.status_code == 429:
        retry_after = int(response.headers.get('Retry-After', 60))
        print(f"Rate limited. Waiting {retry_after} seconds...")
        time.sleep(retry_after)
        return make_request_with_rate_limit(url, headers)
    
    return response.json()

Node.js

javascript
const axios = require('axios');

async function makeRequest(url, apiKey) {
  try {
    const response = await axios.get(url, {
      headers: {
        'X-STOREFRONT-KEY': apiKey,
      },
    });

    const limit = response.headers['x-ratelimit-limit'];
    const remaining = response.headers['x-ratelimit-remaining'];
    const reset = response.headers['x-ratelimit-reset'];

    console.log(`Rate Limit: ${remaining}/${limit}`);
    console.log(`Resets at: ${new Date(reset * 1000)}`);

    return response.data;
  } catch (error) {
    if (error.response?.status === 429) {
      const retryAfter = error.response.headers['retry-after'];
      console.error(`Rate limited. Retry after ${retryAfter} seconds`);
      
      await new Promise(r => setTimeout(r, retryAfter * 1000));
      return makeRequest(url, apiKey);
    }

    throw error;
  }
}

Troubleshooting

Frequently Hitting Rate Limits?

  1. Check your request volume: Review your API usage patterns
  2. Implement caching: Cache responses to reduce API calls
  3. Request higher limit: Contact support for rate limit increase
  4. Batch requests: Combine multiple operations into single calls
  5. Optimize queries: Use pagination to fetch data efficiently

Rate Limit Not Resetting

  • Verify your server time is synchronized (use NTP)
  • Check if you're sending requests with different API keys
  • Verify the X-RateLimit-Reset timestamp (Unix format)

Getting 429 Errors Immediately

  • Check if your API key has a very low rate limit
  • Verify you're sending the correct X-STOREFRONT-KEY header
  • Wait until the reset time before retrying

What's Next?

Released under the MIT License.