# Rate Limiting The API package provides tier-based rate limiting with Redis backend, custom limits per endpoint, and automatic enforcement. ## Overview Rate limiting: - Prevents API abuse - Ensures fair usage - Protects server resources - Enforces tier limits ## Tier-Based Limits Configure limits per tier: ```php // config/api.php 'rate_limits' => [ 'free' => [ 'requests' => 1000, 'per' => 'hour', ], 'pro' => [ 'requests' => 10000, 'per' => 'hour', ], 'business' => [ 'requests' => 50000, 'per' => 'hour', ], 'enterprise' => [ 'requests' => null, // Unlimited ], ], ``` ## Response Headers Every response includes rate limit headers: ``` X-RateLimit-Limit: 10000 X-RateLimit-Remaining: 9847 X-RateLimit-Reset: 1640995200 ``` ## Applying Rate Limits ### Global Rate Limiting ```php // Apply to all API routes Route::middleware('api.rate-limit')->group(function () { Route::get('/posts', [PostController::class, 'index']); Route::post('/posts', [PostController::class, 'store']); }); ``` ### Per-Endpoint Limits ```php // Custom limit for specific endpoint Route::get('/search', [SearchController::class, 'index']) ->middleware('throttle:60,1'); // 60 per minute ``` ### Named Rate Limiters ```php // app/Providers/RouteServiceProvider.php use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Support\Facades\RateLimiter; RateLimiter::for('api', function (Request $request) { return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip()); }); // Apply in routes Route::middleware('throttle:api')->group(function () { // Routes }); ``` ## Custom Rate Limiting ### Based on API Key Tier ```php use Mod\Api\Services\RateLimitService; $rateLimitService = app(RateLimitService::class); $result = $rateLimitService->attempt($apiKey); if ($result->exceeded()) { return response()->json([ 'error' => 'Rate limit exceeded', 'retry_after' => $result->retryAfter(), ], 429); } ``` ### Dynamic Limits ```php RateLimiter::for('api', function (Request $request) { $apiKey = $request->user()->currentApiKey(); return match ($apiKey->rate_limit_tier) { 'free' => Limit::perHour(1000), 'pro' => Limit::perHour(10000), 'business' => Limit::perHour(50000), 'enterprise' => Limit::none(), }; }); ``` ## Rate Limit Responses ### 429 Too Many Requests ```json { "message": "Too many requests", "error_code": "RATE_LIMIT_EXCEEDED", "retry_after": 3600, "limit": 10000, "remaining": 0, "reset_at": "2024-01-15T12:00:00Z" } ``` ### Retry-After Header ``` HTTP/1.1 429 Too Many Requests Retry-After: 3600 X-RateLimit-Limit: 10000 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1640995200 ``` ## Monitoring ### Check Current Usage ```php use Mod\Api\Services\RateLimitService; $service = app(RateLimitService::class); $usage = $service->getCurrentUsage($apiKey); echo "Used: {$usage->used} / {$usage->limit}"; echo "Remaining: {$usage->remaining}"; echo "Resets at: {$usage->reset_at}"; ``` ### Usage Analytics ```php $apiKey = ApiKey::find($id); $stats = $apiKey->usage() ->whereBetween('created_at', [now()->subDays(7), now()]) ->selectRaw('DATE(created_at) as date, COUNT(*) as count') ->groupBy('date') ->get(); ``` ## Best Practices ### 1. Handle 429 Gracefully ```javascript // ✅ Good - retry with backoff async function apiRequest(url, retries = 3) { for (let i = 0; i < retries; i++) { const response = await fetch(url); if (response.status === 429) { const retryAfter = parseInt(response.headers.get('Retry-After')); await sleep(retryAfter * 1000); continue; } return response; } } ``` ### 2. Respect Rate Limit Headers ```javascript // ✅ Good - check remaining requests const remaining = parseInt(response.headers.get('X-RateLimit-Remaining')); if (remaining < 10) { console.warn('Approaching rate limit'); } ``` ### 3. Implement Exponential Backoff ```javascript // ✅ Good - exponential backoff async function fetchWithBackoff(url, maxRetries = 5) { for (let i = 0; i < maxRetries; i++) { const response = await fetch(url); if (response.status !== 429) { return response; } const delay = Math.min(1000 * Math.pow(2, i), 30000); await sleep(delay); } } ``` ### 4. Use Caching ```javascript // ✅ Good - cache responses const cache = new Map(); async function fetchPost(id) { const cached = cache.get(id); if (cached && Date.now() - cached.timestamp < 60000) { return cached.data; } const response = await fetch(`/api/v1/posts/${id}`); const data = await response.json(); cache.set(id, {data, timestamp: Date.now()}); return data; } ``` ## Learn More - [API Authentication →](/packages/api/authentication) - [Error Handling →](/api/errors) - [API Reference →](/api/endpoints#rate-limiting)