> ## Documentation Index
> Fetch the complete documentation index at: https://docs.satsuma.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate Limits

> Understanding API rate limits, quotas, and optimization strategies

## Overview

Satsuma implements rate limiting to ensure fair usage and maintain service quality for all users. This guide explains how rate limits work, how to handle them gracefully, and strategies to optimize your API usage.

## Rate Limit Structure

### Request-based Limits

Rate limits are enforced per API key across different time windows:

* **Per Second**: Short-term burst protection
* **Per Minute**: Medium-term usage smoothing
* **Per Hour**: Long-term quota management
* **Per Day**: Daily usage caps

### Tiered Limits by Plan

<AccordionGroup>
  <Accordion title="Free Tier">
    * **Requests**: 1,000 per month
    * **Rate**: 5 requests per second
    * **Burst**: 10 requests per 10 seconds
    * **Quota Reset**: Monthly on signup date
  </Accordion>

  <Accordion title="Developer - $49/month">
    * **Requests**: 50,000 per month
    * **Rate**: 25 requests per second
    * **Burst**: 100 requests per 10 seconds
    * **Quota Reset**: Monthly on billing date
  </Accordion>

  <Accordion title="Business - $199/month">
    * **Requests**: 500,000 per month
    * **Rate**: 100 requests per second
    * **Burst**: 500 requests per 10 seconds
    * **Quota Reset**: Monthly on billing date
  </Accordion>

  <Accordion title="Enterprise - Custom">
    * **Requests**: Unlimited or custom limit
    * **Rate**: Custom rates up to 1,000 RPS
    * **Burst**: Negotiated burst capacity
    * **Quota Reset**: Configurable
  </Accordion>
</AccordionGroup>

## Rate Limit Headers

Every API response includes rate limit information in the headers:

```http theme={null}
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95  
X-RateLimit-Reset: 1642678800
X-RateLimit-Retry-After: 15
```

### Header Definitions

* `X-RateLimit-Limit`: Maximum requests allowed in current window
* `X-RateLimit-Remaining`: Requests remaining in current window
* `X-RateLimit-Reset`: Unix timestamp when the limit resets
* `X-RateLimit-Retry-After`: Seconds to wait before retrying (429 responses only)

## Handling Rate Limit Responses

### 429 Rate Limit Exceeded

When you exceed rate limits, the API returns a 429 status with details:

```json theme={null}
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Please retry after 60 seconds.",
    "limit": 100,
    "remaining": 0,
    "reset": 1642678800,
    "retry_after": 60
  }
}
```

### Implementing Retry Logic

<CodeGroup>
  ```bash curl theme={null}
  # Basic request with retry logic using curl
  curl -w "HTTP %{http_code}\nLimit: %{header_x-ratelimit-limit}\nRemaining: %{header_x-ratelimit-remaining}\nRetry-After: %{header_x-ratelimit-retry-after}\n" \
    -H "Authorization: your-api-key" \
    -H "Content-Type: application/json" \
    https://api.satsuma.ai/v1/products

  # Retry with exponential backoff script
  #!/bin/bash
  max_retries=3
  attempt=0

  while [ $attempt -le $max_retries ]; do
    response=$(curl -s -w "HTTPSTATUS:%{http_code}" \
      -H "Authorization: your-api-key" \
      https://api.satsuma.ai/v1/products)
    
    http_status=$(echo $response | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
    
    if [ $http_status -eq 429 ]; then
      if [ $attempt -eq $max_retries ]; then
        echo "Max retry attempts exceeded"
        exit 1
      fi
      
      delay=$((2**attempt))
      echo "Rate limited. Retrying in $delay seconds..."
      sleep $delay
      ((attempt++))
    else
      echo $response | sed -e 's/HTTPSTATUS:.*//g'
      exit 0
    fi
  done
  ```

  ```python Python theme={null}
  import asyncio
  import time
  from typing import Optional

  class RateLimitHandler:
      def __init__(self, max_retries: int = 3):
          self.max_retries = max_retries
      
      async def make_request(self, session, method: str, url: str, **kwargs):
          for attempt in range(self.max_retries + 1):
              try:
                  async with session.request(method, url, **kwargs) as response:
                      # Check rate limit status
                      self.log_rate_limit_status(response.headers)
                      
                      if response.status == 429:
                          retry_after = int(response.headers.get('X-RateLimit-Retry-After', 60))
                          
                          if attempt == self.max_retries:
                              raise Exception('Max retry attempts exceeded')
                          
                          print(f"Rate limited. Retrying in {retry_after} seconds...")
                          await asyncio.sleep(retry_after)
                          continue
                      
                      return response
                      
              except Exception as e:
                  if attempt == self.max_retries:
                      raise e
                  await asyncio.sleep(2 ** attempt)
      
      def log_rate_limit_status(self, headers):
          remaining = headers.get('X-RateLimit-Remaining')
          limit = headers.get('X-RateLimit-Limit')
          
          if remaining and int(remaining) < 10:
              print(f"Rate limit approaching: {remaining}/{limit} remaining")
  ```

  ```typescript TypeScript theme={null}
  class RateLimitHandler {
    private maxRetries: number;

    constructor(maxRetries: number = 3) {
      this.maxRetries = maxRetries;
    }

    async makeRequest(url: string, options: RequestInit): Promise<Response> {
      for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
        try {
          const response = await fetch(url, options);
          
          // Check rate limit headers
          this.logRateLimitStatus(response);
          
          if (response.status === 429) {
            const retryAfter = response.headers.get('X-RateLimit-Retry-After') || '60';
            
            if (attempt === this.maxRetries) {
              throw new Error('Max retry attempts exceeded');
            }
            
            console.log(`Rate limited. Retrying in ${retryAfter} seconds...`);
            await this.sleep(parseInt(retryAfter) * 1000);
            continue;
          }
          
          return response;
        } catch (error) {
          if (attempt === this.maxRetries) throw error;
          await this.sleep(Math.pow(2, attempt) * 1000);
        }
      }
      
      throw new Error('All retry attempts failed');
    }

    private logRateLimitStatus(response: Response): void {
      const limit = response.headers.get('X-RateLimit-Limit');
      const remaining = response.headers.get('X-RateLimit-Remaining');
      const reset = response.headers.get('X-RateLimit-Reset');
      
      if (remaining && parseInt(remaining) < 10) {
        console.warn(`Rate limit approaching: ${remaining}/${limit} remaining`);
      }
    }

    private sleep(ms: number): Promise<void> {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
  }
  ```

  ```java Java theme={null}
  import java.io.IOException;
  import java.net.URI;
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;
  import java.time.Duration;
  import java.util.Optional;

  public class RateLimitHandler {
      private final int maxRetries;
      private final HttpClient client;
      
      public RateLimitHandler(int maxRetries) {
          this.maxRetries = maxRetries;
          this.client = HttpClient.newBuilder()
              .connectTimeout(Duration.ofSeconds(30))
              .build();
      }
      
      public HttpResponse<String> makeRequest(HttpRequest request) 
              throws IOException, InterruptedException {
          for (int attempt = 0; attempt <= maxRetries; attempt++) {
              try {
                  HttpResponse<String> response = client.send(request, 
                      HttpResponse.BodyHandlers.ofString());
                  
                  // Check rate limit status
                  logRateLimitStatus(response);
                  
                  if (response.statusCode() == 429) {
                      String retryAfter = response.headers()
                          .firstValue("X-RateLimit-Retry-After")
                          .orElse("60");
                      
                      if (attempt == maxRetries) {
                          throw new RuntimeException("Max retry attempts exceeded");
                      }
                      
                      int delay = Integer.parseInt(retryAfter);
                      System.out.println("Rate limited. Retrying in " + delay + " seconds...");
                      Thread.sleep(delay * 1000L);
                      continue;
                  }
                  
                  return response;
              } catch (IOException | InterruptedException e) {
                  if (attempt == maxRetries) throw e;
                  Thread.sleep((long) Math.pow(2, attempt) * 1000);
              }
          }
          
          throw new RuntimeException("All retry attempts failed");
      }
      
      private void logRateLimitStatus(HttpResponse<String> response) {
          Optional<String> remaining = response.headers()
              .firstValue("X-RateLimit-Remaining");
          Optional<String> limit = response.headers()
              .firstValue("X-RateLimit-Limit");
          
          if (remaining.isPresent() && Integer.parseInt(remaining.get()) < 10) {
              System.out.println("Rate limit approaching: " + remaining.get() + "/" + 
                  limit.orElse("unknown") + " remaining");
          }
      }
  }
  ```
</CodeGroup>

## Optimization Strategies

### Request Batching

Combine multiple operations into single requests when possible:

<CodeGroup>
  ```bash curl theme={null}
  # Instead of multiple individual requests:
  # curl -H "Authorization: your-api-key" https://api.satsuma.ai/v1/products/prod_123
  # curl -H "Authorization: your-api-key" https://api.satsuma.ai/v1/products/prod_456
  # curl -H "Authorization: your-api-key" https://api.satsuma.ai/v1/products/prod_789

  # Use batch endpoint:
  curl -X POST \
    -H "Authorization: your-api-key" \
    -H "Content-Type: application/json" \
    -d '{"product_ids": ["prod_123", "prod_456", "prod_789"]}' \
    https://api.satsuma.ai/v1/products/batch
  ```

  ```python Python theme={null}
  # Instead of multiple individual requests
  product1 = await api.get_product('prod_123')
  product2 = await api.get_product('prod_456')
  product3 = await api.get_product('prod_789')

  # Use batch endpoint
  products = await api.get_products_batch(['prod_123', 'prod_456', 'prod_789'])
  ```

  ```typescript TypeScript theme={null}
  // Instead of multiple individual requests
  const product1 = await api.getProduct('prod_123');
  const product2 = await api.getProduct('prod_456');
  const product3 = await api.getProduct('prod_789');

  // Use batch endpoint
  const products = await api.getProductsBatch(['prod_123', 'prod_456', 'prod_789']);
  ```

  ```java Java theme={null}
  // Instead of multiple individual requests
  Product product1 = api.getProduct("prod_123");
  Product product2 = api.getProduct("prod_456");
  Product product3 = api.getProduct("prod_789");

  // Use batch endpoint
  List<String> productIds = Arrays.asList("prod_123", "prod_456", "prod_789");
  List<Product> products = api.getProductsBatch(productIds);
  ```
</CodeGroup>

### Caching Strategies

Implement intelligent caching to reduce API calls:

<CodeGroup>
  ```bash curl theme={null}
  # Simple file-based caching with curl
  #!/bin/bash
  cache_dir="/tmp/satsuma_cache"
  mkdir -p "$cache_dir"

  get_product_cached() {
    local product_id="$1"
    local cache_file="$cache_dir/product_$product_id"
    local ttl=300  # 5 minutes
    
    # Check if cache exists and is fresh
    if [ -f "$cache_file" ] && [ $(($(date +%s) - $(stat -c %Y "$cache_file"))) -lt $ttl ]; then
      cat "$cache_file"
      return 0
    fi
    
    # Fetch from API and cache
    curl -s -H "Authentication: your-api-key" \
      "https://api.satsuma.ai/v1/products/$product_id" | tee "$cache_file"
  }

  # Usage
  get_product_cached "prod_123"
  ```

  ```python Python theme={null}
  import time
  from typing import Dict, Any, Optional

  class CachedApiClient:
      def __init__(self, ttl: int = 300):
          self.cache: Dict[str, Dict[str, Any]] = {}
          self.ttl = ttl  # 5 minute default TTL
      
      async def get_product(self, product_id: str) -> Dict[str, Any]:
          cache_key = f"product:{product_id}"
          cached = self.cache.get(cache_key)
          
          if cached and time.time() - cached['timestamp'] < self.ttl:
              return cached['data']
          
          product = await self.api_client.get_product(product_id)
          
          self.cache[cache_key] = {
              'data': product,
              'timestamp': time.time()
          }
          
          return product
      
      def invalidate_cache(self, pattern: str) -> None:
          keys_to_delete = [key for key in self.cache.keys() if pattern in key]
          for key in keys_to_delete:
              del self.cache[key]
  ```

  ```typescript TypeScript theme={null}
  interface CacheEntry<T> {
    data: T;
    timestamp: number;
  }

  class CachedApiClient {
    private cache = new Map<string, CacheEntry<any>>();
    private ttl: number;

    constructor(ttl: number = 300000) { // 5 minute default TTL
      this.ttl = ttl;
    }

    async getProduct(productId: string): Promise<any> {
      const cacheKey = `product:${productId}`;
      const cached = this.cache.get(cacheKey);
      
      if (cached && Date.now() - cached.timestamp < this.ttl) {
        return cached.data;
      }
      
      const product = await this.apiClient.getProduct(productId);
      
      this.cache.set(cacheKey, {
        data: product,
        timestamp: Date.now()
      });
      
      return product;
    }

    invalidateCache(pattern: string): void {
      for (const key of this.cache.keys()) {
        if (key.includes(pattern)) {
          this.cache.delete(key);
        }
      }
    }
  }
  ```

  ```java Java theme={null}
  import java.time.Instant;
  import java.util.Map;
  import java.util.concurrent.ConcurrentHashMap;

  public class CachedApiClient {
      private final Map<String, CacheEntry> cache = new ConcurrentHashMap<>();
      private final long ttl;
      private final ApiClient apiClient;
      
      public CachedApiClient(long ttlSeconds) {
          this.ttl = ttlSeconds;
          this.apiClient = new ApiClient();
      }
      
      public Product getProduct(String productId) {
          String cacheKey = "product:" + productId;
          CacheEntry cached = cache.get(cacheKey);
          
          if (cached != null && 
              Instant.now().getEpochSecond() - cached.timestamp < ttl) {
              return (Product) cached.data;
          }
          
          Product product = apiClient.getProduct(productId);
          
          cache.put(cacheKey, new CacheEntry(
              product, 
              Instant.now().getEpochSecond()
          ));
          
          return product;
      }
      
      public void invalidateCache(String pattern) {
          cache.entrySet().removeIf(entry -> 
              entry.getKey().contains(pattern));
      }
      
      private static class CacheEntry {
          final Object data;
          final long timestamp;
          
          CacheEntry(Object data, long timestamp) {
              this.data = data;
              this.timestamp = timestamp;
          }
      }
  }
  ```
</CodeGroup>

### Connection Pooling

Reuse HTTP connections to reduce overhead:

<CodeGroup>
  ```bash curl theme={null}
  # curl automatically reuses connections with --keepalive-time
  # Use connection pooling with parallel requests
  curl --parallel --parallel-max 10 --keepalive-time 60 \
    -H "Authorization: your-api-key" \
    https://api.satsuma.ai/v1/products/[prod_123,prod_456,prod_789]

  # For scripts, use curl with connection reuse
  export CURL_OPTS="--keepalive-time 60 --max-time 30"
  curl $CURL_OPTS -H "Authorization: your-api-key" https://api.satsuma.ai/v1/products
  ```

  ```python Python theme={null}
  import aiohttp
  import asyncio

  # Connection pooling with aiohttp
  connector = aiohttp.TCPConnector(
      limit=100,  # Total pool size
      limit_per_host=30,  # Per host pool size
      keepalive_timeout=60,
      enable_cleanup_closed=True
  )

  session = aiohttp.ClientSession(
      connector=connector,
      timeout=aiohttp.ClientTimeout(total=30)
  )

  # Use session for all requests
  async def make_requests():
      async with session.get(
          'https://api.satsuma.ai/v1/products',
          headers={'Authorization': 'your-api-key'}
      ) as response:
          return await response.json()
  ```

  ```typescript TypeScript theme={null}
  import https from 'https';
  import axios from 'axios';

  // Create HTTPS agent with connection pooling
  const agent = new https.Agent({
    keepAlive: true,
    maxSockets: 10,
    maxFreeSockets: 5,
    timeout: 60000,
    freeSocketTimeout: 30000
  });

  // Configure axios with connection pooling
  const apiClient = axios.create({
    httpsAgent: agent,
    timeout: 30000,
    maxRedirects: 3
  });

  // Reuse the same client instance
  export { apiClient };
  ```

  ```java Java theme={null}
  import java.net.http.HttpClient;
  import java.time.Duration;
  import java.util.concurrent.Executors;

  public class ApiClientFactory {
      private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
          .connectTimeout(Duration.ofSeconds(30))
          .executor(Executors.newFixedThreadPool(10))
          .version(HttpClient.Version.HTTP_2)
          .followRedirects(HttpClient.Redirect.NORMAL)
          .build();
      
      public static HttpClient getClient() {
          return HTTP_CLIENT;
      }
  }

  // Usage with connection pooling
  public class SatsumaApiClient {
      private final HttpClient client = ApiClientFactory.getClient();
      
      public HttpResponse<String> makeRequest(HttpRequest request) 
              throws IOException, InterruptedException {
          return client.send(request, HttpResponse.BodyHandlers.ofString());
      }
  }
  ```
</CodeGroup>

## Rate Limit Monitoring

### Tracking Usage

Monitor your rate limit usage to avoid surprises:

<CodeGroup>
  ```bash curl theme={null}
  #!/bin/bash
  # Simple rate limit monitoring script
  monitor_rate_limits() {
    local log_file="/tmp/rate_limit_monitor.log"
    
    while true; do
      response=$(curl -s -w "LIMIT:%{header_x-ratelimit-limit}|REMAINING:%{header_x-ratelimit-remaining}|RESET:%{header_x-ratelimit-reset}" \
        -H "Authorization: your-api-key" \
        https://api.satsuma.ai/v1/products 2>&1)
      
      limit=$(echo "$response" | grep -o 'LIMIT:[^|]*' | cut -d: -f2)
      remaining=$(echo "$response" | grep -o 'REMAINING:[^|]*' | cut -d: -f2)
      reset=$(echo "$response" | grep -o 'RESET:[^|]*' | cut -d: -f2)
      
      if [ -n "$limit" ] && [ -n "$remaining" ]; then
        usage_percent=$(( (limit - remaining) * 100 / limit ))
        
        echo "$(date): Limit=$limit, Remaining=$remaining, Usage=${usage_percent}%" >> "$log_file"
        
        if [ $usage_percent -gt 80 ]; then
          echo "WARNING: Rate limit usage at ${usage_percent}%"
        fi
      fi
      
      sleep 60
    done
  }
  ```

  ```python Python theme={null}
  import time
  from typing import Dict, List
  from collections import defaultdict

  class RateLimitMonitor:
      def __init__(self):
          self.metrics = {
              'requests_per_second': [],
              'daily_usage': 0,
              'near_limit_warnings': 0
          }
      
      def record_request(self, response_headers: Dict[str, str]) -> None:
          now = time.time()
          limit = int(response_headers.get('X-RateLimit-Limit', 0))
          remaining = int(response_headers.get('X-RateLimit-Remaining', 0))
          reset = int(response_headers.get('X-RateLimit-Reset', 0))
          
          # Track requests per second
          self.metrics['requests_per_second'].append(now)
          self.clean_old_metrics()
          
          # Check if approaching limit
          if limit > 0:
              usage_percent = ((limit - remaining) / limit) * 100
              if usage_percent > 80:
                  self.metrics['near_limit_warnings'] += 1
                  print(f"Rate limit usage at {usage_percent:.1f}%")
          
          # Estimate time until reset
          time_until_reset = reset - now
          if time_until_reset > 0 and remaining < 10:
              print(f"Only {remaining} requests remaining for {time_until_reset:.0f}s")
      
      def clean_old_metrics(self) -> None:
          one_second_ago = time.time() - 1
          self.metrics['requests_per_second'] = [
              ts for ts in self.metrics['requests_per_second'] 
              if ts > one_second_ago
          ]
      
      def get_current_rps(self) -> int:
          return len(self.metrics['requests_per_second'])
  ```

  ```typescript TypeScript theme={null}
  interface RateLimitMetrics {
    requestsPerSecond: number[];
    dailyUsage: number;
    nearLimitWarnings: number;
  }

  class RateLimitMonitor {
    private metrics: RateLimitMetrics;

    constructor() {
      this.metrics = {
        requestsPerSecond: [],
        dailyUsage: 0,
        nearLimitWarnings: 0
      };
    }

    recordRequest(response: Response): void {
      const now = Date.now();
      const limit = parseInt(response.headers.get('X-RateLimit-Limit') || '0');
      const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0');
      const reset = parseInt(response.headers.get('X-RateLimit-Reset') || '0') * 1000;
      
      // Track requests per second
      this.metrics.requestsPerSecond.push(now);
      this.cleanOldMetrics();
      
      // Check if approaching limit
      if (limit > 0) {
        const usagePercent = ((limit - remaining) / limit) * 100;
        if (usagePercent > 80) {
          this.metrics.nearLimitWarnings++;
          console.warn(`Rate limit usage at ${usagePercent.toFixed(1)}%`);
        }
      }
      
      // Estimate time until reset
      const timeUntilReset = reset - now;
      if (timeUntilReset > 0 && remaining < 10) {
        console.warn(`Only ${remaining} requests remaining for ${timeUntilReset / 1000}s`);
      }
    }

    private cleanOldMetrics(): void {
      const oneSecondAgo = Date.now() - 1000;
      this.metrics.requestsPerSecond = this.metrics.requestsPerSecond
        .filter(timestamp => timestamp > oneSecondAgo);
    }

    getCurrentRPS(): number {
      return this.metrics.requestsPerSecond.length;
    }
  }
  ```

  ```java Java theme={null}
  import java.time.Instant;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;
  import java.util.concurrent.ConcurrentHashMap;

  public class RateLimitMonitor {
      private final List<Long> requestsPerSecond = new ArrayList<>();
      private final Map<String, Integer> metrics = new ConcurrentHashMap<>();
      
      public RateLimitMonitor() {
          metrics.put("dailyUsage", 0);
          metrics.put("nearLimitWarnings", 0);
      }
      
      public void recordRequest(HttpResponse<String> response) {
          long now = Instant.now().toEpochMilli();
          
          int limit = response.headers().firstValue("X-RateLimit-Limit")
              .map(Integer::parseInt).orElse(0);
          int remaining = response.headers().firstValue("X-RateLimit-Remaining")
              .map(Integer::parseInt).orElse(0);
          long reset = response.headers().firstValue("X-RateLimit-Reset")
              .map(Long::parseLong).orElse(0L) * 1000;
          
          // Track requests per second
          synchronized (requestsPerSecond) {
              requestsPerSecond.add(now);
              cleanOldMetrics();
          }
          
          // Check if approaching limit
          if (limit > 0) {
              double usagePercent = ((double)(limit - remaining) / limit) * 100;
              if (usagePercent > 80) {
                  metrics.put("nearLimitWarnings", 
                      metrics.get("nearLimitWarnings") + 1);
                  System.out.printf("Rate limit usage at %.1f%%\n", usagePercent);
              }
          }
          
          // Estimate time until reset
          long timeUntilReset = reset - now;
          if (timeUntilReset > 0 && remaining < 10) {
              System.out.printf("Only %d requests remaining for %ds\n", 
                  remaining, timeUntilReset / 1000);
          }
      }
      
      private void cleanOldMetrics() {
          long oneSecondAgo = Instant.now().toEpochMilli() - 1000;
          requestsPerSecond.removeIf(timestamp -> timestamp <= oneSecondAgo);
      }
      
      public int getCurrentRPS() {
          synchronized (requestsPerSecond) {
              return requestsPerSecond.size();
          }
      }
  }
  ```
</CodeGroup>

### Alerting on High Usage

Set up alerts when approaching rate limits:

<CodeGroup>
  ```bash curl theme={null}
  #!/bin/bash
  # Rate limit alerting script
  alert_threshold=80
  cooldown_seconds=300
  last_alert_file="/tmp/last_rate_limit_alert"

  check_and_alert() {
    local limit="$1"
    local remaining="$2"
    
    if [ "$limit" -gt 0 ]; then
      local usage_percent=$(( (limit - remaining) * 100 / limit ))
      
      if [ $usage_percent -ge $alert_threshold ]; then
        local now=$(date +%s)
        local last_alert=0
        
        [ -f "$last_alert_file" ] && last_alert=$(cat "$last_alert_file")
        
        if [ $((now - last_alert)) -gt $cooldown_seconds ]; then
          echo "ALERT: Rate limit usage at ${usage_percent}%" | \
            mail -s "Satsuma API Rate Limit Warning" admin@company.com
          
          # Log to syslog
          logger "Rate limit alert: ${usage_percent}% usage, ${remaining}/${limit} remaining"
          
          echo "$now" > "$last_alert_file"
        fi
      fi
    fi
  }

  # Usage
  response=$(curl -s -w "LIMIT:%{header_x-ratelimit-limit}|REMAINING:%{header_x-ratelimit-remaining}" \
    -H "Authorization: your-api-key" https://api.satsuma.ai/v1/products)
  limit=$(echo "$response" | grep -o 'LIMIT:[^|]*' | cut -d: -f2)
  remaining=$(echo "$response" | grep -o 'REMAINING:[^|]*' | cut -d: -f2)
  check_and_alert "$limit" "$remaining"
  ```

  ```python Python theme={null}
  import time
  import logging
  from typing import Dict, Any

  class RateLimitAlerting:
      def __init__(self, alert_threshold: float = 0.8):
          self.alert_threshold = alert_threshold
          self.alert_cooldown = 300  # 5 minutes
          self.last_alert = 0
          self.logger = logging.getLogger(__name__)
      
      def check_and_alert(self, limit: int, remaining: int) -> None:
          if limit == 0:
              return
              
          usage_percent = (limit - remaining) / limit
          
          if usage_percent >= self.alert_threshold:
              now = time.time()
              
              if now - self.last_alert > self.alert_cooldown:
                  alert_data = {
                      'level': 'warning',
                      'message': f'Rate limit usage at {usage_percent * 100:.1f}%',
                      'limit': limit,
                      'remaining': remaining,
                      'timestamp': now
                  }
                  
                  self.send_alert(alert_data)
                  self.last_alert = now
      
      def send_alert(self, alert_data: Dict[str, Any]) -> None:
          # Log the alert
          self.logger.warning('Rate limit alert: %s', alert_data)
          
          # Send to monitoring service (implement as needed)
          if alert_data['level'] == 'critical':
              self.notify_oncall(alert_data)
      
      def notify_oncall(self, alert_data: Dict[str, Any]) -> None:
          # Implement critical alerting (PagerDuty, Slack, etc.)
          print(f"CRITICAL ALERT: {alert_data['message']}")
  ```

  ```typescript TypeScript theme={null}
  interface AlertData {
    level: 'warning' | 'critical';
    message: string;
    limit: number;
    remaining: number;
    timestamp: number;
  }

  class RateLimitAlerting {
    private alertThreshold: number;
    private alertCooldown: number; // 5 minutes
    private lastAlert: number;

    constructor(alertThreshold: number = 0.8) {
      this.alertThreshold = alertThreshold;
      this.alertCooldown = 300000;
      this.lastAlert = 0;
    }

    checkAndAlert(limit: number, remaining: number): void {
      if (limit === 0) return;
      
      const usagePercent = (limit - remaining) / limit;
      
      if (usagePercent >= this.alertThreshold) {
        const now = Date.now();
        
        if (now - this.lastAlert > this.alertCooldown) {
          const alertData: AlertData = {
            level: 'warning',
            message: `Rate limit usage at ${(usagePercent * 100).toFixed(1)}%`,
            limit,
            remaining,
            timestamp: now
          };
          
          this.sendAlert(alertData);
          this.lastAlert = now;
        }
      }
    }

    private sendAlert(alertData: AlertData): void {
      // Send to monitoring service
      console.warn('Rate limit alert:', alertData);
      
      // Could also send to Slack, PagerDuty, etc.
      if (alertData.level === 'critical') {
        this.notifyOncall(alertData);
      }
    }

    private notifyOncall(alertData: AlertData): void {
      // Implement critical alerting
      console.error('CRITICAL ALERT:', alertData.message);
    }
  }
  ```

  ```java Java theme={null}
  import java.time.Instant;
  import java.util.logging.Logger;

  public class RateLimitAlerting {
      private final double alertThreshold;
      private final long alertCooldown; // 5 minutes in milliseconds
      private long lastAlert;
      private final Logger logger = Logger.getLogger(getClass().getName());
      
      public RateLimitAlerting(double alertThreshold) {
          this.alertThreshold = alertThreshold;
          this.alertCooldown = 300000L;
          this.lastAlert = 0;
      }
      
      public void checkAndAlert(int limit, int remaining) {
          if (limit == 0) return;
          
          double usagePercent = (double)(limit - remaining) / limit;
          
          if (usagePercent >= alertThreshold) {
              long now = Instant.now().toEpochMilli();
              
              if (now - lastAlert > alertCooldown) {
                  AlertData alertData = new AlertData(
                      "warning",
                      String.format("Rate limit usage at %.1f%%", usagePercent * 100),
                      limit,
                      remaining,
                      now
                  );
                  
                  sendAlert(alertData);
                  lastAlert = now;
              }
          }
      }
      
      private void sendAlert(AlertData alertData) {
          // Log the alert
          logger.warning("Rate limit alert: " + alertData.message);
          
          // Send to monitoring service
          System.err.println("Rate limit alert: " + alertData);
          
          // Critical alerting
          if ("critical".equals(alertData.level)) {
              notifyOncall(alertData);
          }
      }
      
      private void notifyOncall(AlertData alertData) {
          // Implement critical alerting (PagerDuty, Slack, etc.)
          System.err.println("CRITICAL ALERT: " + alertData.message);
      }
      
      private static class AlertData {
          final String level;
          final String message;
          final int limit;
          final int remaining;
          final long timestamp;
          
          AlertData(String level, String message, int limit, int remaining, long timestamp) {
              this.level = level;
              this.message = message;
              this.limit = limit;
              this.remaining = remaining;
              this.timestamp = timestamp;
          }
          
          @Override
          public String toString() {
              return String.format("AlertData{level='%s', message='%s', limit=%d, remaining=%d}",
                  level, message, limit, remaining);
          }
      }
  }
  ```
</CodeGroup>

## Plan Management

### Upgrading Plans

When you consistently hit rate limits:

<Steps>
  <Step title="Monitor Usage Patterns">
    Track your API usage over time to understand peak load requirements
  </Step>

  <Step title="Calculate ROI">
    Compare the cost of API rate limit delays vs. higher plan pricing
  </Step>

  <Step title="Upgrade Plan">
    Visit the [billing section](https://dashboard.satsuma.ai/billing) to upgrade
  </Step>

  <Step title="Verify Limits">
    Test that new limits meet your application's needs
  </Step>
</Steps>

### Custom Enterprise Limits

For high-volume applications, contact our sales team to discuss:

* **Custom rate limits** above standard plans
* **Burst capacity** for traffic spikes
* **Regional rate limits** for global applications
* **Dedicated infrastructure** for consistent performance

## Best Practices

### Request Timing

* **Spread requests evenly** instead of bursts when possible
* **Use jitter** in retry delays to avoid thundering herd effects
* **Queue non-urgent requests** during high-traffic periods
* **Implement circuit breakers** to prevent cascade failures

### Error Handling

* **Always check rate limit headers** before making next request
* **Implement exponential backoff** with a maximum delay
* **Log rate limit events** for monitoring and optimization
* **Gracefully degrade functionality** when limits are reached

### Architecture Considerations

* **Cache frequently accessed data** to reduce API calls
* **Use webhooks** instead of polling for real-time updates
* **Implement request queuing** for batch operations
* **Consider async processing** for non-real-time operations

## Troubleshooting

### Common Rate Limit Issues

<AccordionGroup>
  <Accordion title="Unexpected 429 errors on low usage">
    * Check if you have multiple processes/servers using the same API key
    * Verify you're not making concurrent requests that exceed per-second limits
    * Review your request batching and caching implementation
    * Check if your plan has sufficient rate limits for your usage pattern
  </Accordion>

  <Accordion title="Rate limits reset unexpectedly">
    * Different rate limit windows (per-second, per-minute, per-hour) have different reset times
    * Monthly quotas reset on your billing date, not calendar month
    * Upgrading plans may cause immediate limit increases
    * Contact support if you see inconsistent reset behavior
  </Accordion>

  <Accordion title="High latency during rate limiting">
    * Implement proper backoff strategies instead of aggressive retries
    * Use connection pooling to reduce connection establishment overhead
    * Consider upgrading to a higher plan for better rate limits
    * Monitor for server-side rate limiting vs. client-side queuing delays
  </Accordion>
</AccordionGroup>

### Rate Limit Debugging

Use these tools to debug rate limit issues:

<CodeGroup>
  ```bash curl theme={null}
  # Check current rate limit status
  curl -I -H "Authorization: your-key" https://api.satsuma.ai/v1/products

  # Monitor rate limit headers in real-time
  curl -w "Limit: %{header_x-ratelimit-limit}\nRemaining: %{header_x-ratelimit-remaining}\nReset: %{header_x-ratelimit-reset}\nRetry-After: %{header_x-ratelimit-retry-after}\n" \
    -H "Authorization: your-key" \
    https://api.satsuma.ai/v1/products

  # Debug rate limiting with verbose output
  curl -v -H "Authorization: your-key" https://api.satsuma.ai/v1/products 2>&1 | \
    grep -E "(X-RateLimit|HTTP/)"
  ```

  ```python Python theme={null}
  import requests

  def debug_rate_limits(api_key: str):
      headers = {'Authorization': api_key}
      
      try:
          response = requests.get(
              'https://api.satsuma.ai/product',
              headers=headers,
              params={'latitude': 37.7749, 'longitude': -122.4194, 'distance': '10mi'}
          )
          
          print(f"Status Code: {response.status_code}")
          print(f"Rate Limit: {response.headers.get('X-RateLimit-Limit')}")
          print(f"Remaining: {response.headers.get('X-RateLimit-Remaining')}")
          print(f"Reset: {response.headers.get('X-RateLimit-Reset')}")
          print(f"Retry-After: {response.headers.get('X-RateLimit-Retry-After')}")
          
          if response.status_code == 429:
              print("Rate limited! Response body:")
              print(response.text)
              
      except Exception as e:
          print(f"Request failed: {e}")

  # Usage
  debug_rate_limits('your-api-key')
  ```

  ```typescript TypeScript theme={null}
  async function debugRateLimits(apiKey: string): Promise<void> {
    try {
      const response = await fetch('https://api.satsuma.ai/v1/products', {
        headers: {
          'Authorization': apiKey
        }
      });
      
      console.log(`Status Code: ${response.status}`);
      console.log(`Rate Limit: ${response.headers.get('X-RateLimit-Limit')}`);
      console.log(`Remaining: ${response.headers.get('X-RateLimit-Remaining')}`);
      console.log(`Reset: ${response.headers.get('X-RateLimit-Reset')}`);
      console.log(`Retry-After: ${response.headers.get('X-RateLimit-Retry-After')}`);
      
      if (response.status === 429) {
        console.log('Rate limited! Response body:');
        console.log(await response.text());
      }
      
    } catch (error) {
      console.error(`Request failed: ${error}`);
    }
  }

  // Usage
  debugRateLimits('your-api-key');
  ```

  ```java Java theme={null}
  import java.io.IOException;
  import java.net.URI;
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;

  public class RateLimitDebugger {
      private final HttpClient client = HttpClient.newHttpClient();
      
      public void debugRateLimits(String apiKey) {
          try {
              HttpRequest request = HttpRequest.newBuilder()
                  .uri(URI.create("https://api.satsuma.ai/v1/products"))
                  .header("Authorization", apiKey)
                  .build();
              
              HttpResponse<String> response = client.send(request, 
                  HttpResponse.BodyHandlers.ofString());
              
              System.out.println("Status Code: " + response.statusCode());
              
              response.headers().firstValue("X-RateLimit-Limit")
                  .ifPresent(limit -> System.out.println("Rate Limit: " + limit));
              response.headers().firstValue("X-RateLimit-Remaining")
                  .ifPresent(remaining -> System.out.println("Remaining: " + remaining));
              response.headers().firstValue("X-RateLimit-Reset")
                  .ifPresent(reset -> System.out.println("Reset: " + reset));
              response.headers().firstValue("X-RateLimit-Retry-After")
                  .ifPresent(retryAfter -> System.out.println("Retry-After: " + retryAfter));
              
              if (response.statusCode() == 429) {
                  System.out.println("Rate limited! Response body:");
                  System.out.println(response.body());
              }
              
          } catch (IOException | InterruptedException e) {
              System.err.println("Request failed: " + e.getMessage());
          }
      }
      
      // Usage
      public static void main(String[] args) {
          new RateLimitDebugger().debugRateLimits("your-api-key");
      }
  }
  ```
</CodeGroup>

## Getting Help

For rate limit assistance:

* Review your usage in the [dashboard analytics](https://dashboard.satsuma.ai/analytics)
* Check our [status page](https://status.satsuma.ai) for any rate limiting incidents
* Contact [support@satsuma.ai](mailto:support@satsuma.ai) with your API key prefix and usage details
* For plan upgrades or custom limits, reach out to [sales@satsuma.ai](mailto:sales@satsuma.ai)
