--- name: rate-limiting description: Understanding and handling BFL API rate limits --- # Rate Limiting BFL API enforces rate limits to ensure fair usage and system stability. ## Current Limits | Endpoint Category | Concurrent Requests | | ---------------------- | ------------------- | | Standard (most models) | 24 | **Concurrent requests** means in-flight requests (submitted but not yet completed). ## Rate Limit Headers Check response headers for rate limit status: ``` X-RateLimit-Limit: 24 X-RateLimit-Remaining: 23 X-RateLimit-Reset: 1640000000 ``` ## HTTP 429 Response When rate limited, you receive HTTP 429: ```json { "error": "rate_limit_exceeded", "message": "Too many concurrent requests", "retry_after": 5 } ``` ## Handling Strategies ### 1. Client-Side Tracking Track active requests to stay under limits: ```python from threading import Lock, Semaphore class RateLimitedClient: def __init__(self, api_key, max_concurrent=24): self.api_key = api_key self.semaphore = Semaphore(max_concurrent) def generate(self, model, prompt, **kwargs): with self.semaphore: # Blocks if at limit return self._make_request(model, prompt, **kwargs) def _make_request(self, model, prompt, **kwargs): # Submit request response = requests.post(...) polling_url = response.json()["polling_url"] # Poll until complete (request still "active") return self._poll(polling_url) ``` ### 2. Retry with Exponential Backoff ```python import time def request_with_retry(endpoint, payload, headers, max_retries=5): """Make request with automatic retry on rate limit.""" for attempt in range(max_retries): response = requests.post(endpoint, json=payload, headers=headers) if response.status_code == 429: retry_after = int(response.headers.get('Retry-After', 5)) wait_time = retry_after * (2 ** attempt) # Exponential backoff print(f"Rate limited. Waiting {wait_time}s...") time.sleep(wait_time) continue response.raise_for_status() return response raise Exception("Max retries exceeded due to rate limiting") ``` ### 3. Queue-Based Architecture For high-volume applications: ```python from queue import Queue from threading import Thread import time class RequestQueue: def __init__(self, api_key, max_concurrent=24): self.api_key = api_key self.queue = Queue() self.active = 0 self.max_concurrent = max_concurrent self.lock = Lock() # Start worker threads for _ in range(max_concurrent): worker = Thread(target=self._worker, daemon=True) worker.start() def submit(self, model, prompt, callback): """Submit request to queue.""" self.queue.put({ 'model': model, 'prompt': prompt, 'callback': callback }) def _worker(self): """Process queue items.""" while True: item = self.queue.get() try: result = self._process(item) item['callback'](result, None) except Exception as e: item['callback'](None, e) finally: self.queue.task_done() def _process(self, item): # Make request and poll ... # Usage queue = RequestQueue("your-api-key") def handle_result(result, error): if error: print(f"Error: {error}") else: print(f"Generated: {result['sample']}") queue.submit("flux-2-pro", "A sunset", handle_result) ``` ### 4. Async with Semaphore ```python import asyncio import aiohttp class AsyncRateLimitedClient: def __init__(self, api_key, max_concurrent=24): self.api_key = api_key self.semaphore = asyncio.Semaphore(max_concurrent) self.headers = {"x-key": api_key} async def generate(self, model, prompt): async with self.semaphore: async with aiohttp.ClientSession() as session: # Submit async with session.post( f"https://api.bfl.ai/v1/{model}", headers=self.headers, json={"prompt": prompt} ) as response: data = await response.json() polling_url = data["polling_url"] # Poll until complete while True: async with session.get( polling_url, headers=self.headers ) as response: data = await response.json() if data["status"] == "Ready": return data["result"] elif data["status"] == "Error": raise Exception(data.get("error")) await asyncio.sleep(2) # Usage async def main(): client = AsyncRateLimitedClient("your-api-key") # Generate 50 images with rate limiting prompts = [f"Image {i}" for i in range(50)] tasks = [client.generate("flux-2-pro", p) for p in prompts] results = await asyncio.gather(*tasks) asyncio.run(main()) ``` ## Monitoring Rate Limits ```python class RateLimitMonitor: def __init__(self): self.requests_made = 0 self.rate_limit_hits = 0 self.lock = Lock() def record_request(self, response): with self.lock: self.requests_made += 1 if response.status_code == 429: self.rate_limit_hits += 1 def get_stats(self): return { "total_requests": self.requests_made, "rate_limit_hits": self.rate_limit_hits, "hit_rate": self.rate_limit_hits / max(self.requests_made, 1) } ``` ## Best Practices 1. **Track active requests** - Know how many are in-flight 2. **Implement client-side limits** - Stay under limits proactively 3. **Use semaphores** - Clean way to limit concurrency 4. **Queue for high volume** - Buffer requests when traffic spikes 5. **Monitor headers** - React to remaining quota 6. **Graceful degradation** - Queue or delay when near limits 7. **Different limits per endpoint** - Remember Kontext Max is 6, not 24 ## Regional Distribution For very high volume, consider distributing across regions: ```python ENDPOINTS = [ "https://api.bfl.ai", "https://api.eu.bfl.ai", "https://api.us.bfl.ai" ] def get_endpoint(): """Round-robin or least-loaded selection.""" return random.choice(ENDPOINTS) ``` Note: Verify regional rate limits are independent before relying on this strategy.