Rate Limiting
Relay provides client-side rate limiting to prevent exceeding API quotas and handles server-side 429 responses gracefully.
Connector-Level Rate Limiting
Section titled “Connector-Level Rate Limiting”use Cline\Relay\Core\Connector;use Cline\Relay\Features\RateLimiting\RateLimitConfig;
class TwitterConnector extends Connector{ public function baseUrl(): string { return 'https://api.twitter.com/2'; }
public function rateLimit(): ?RateLimitConfig { return new RateLimitConfig( requests: 300, perSeconds: 900, // 15 minute window ); }}Rate Limit Configuration
Section titled “Rate Limit Configuration”$config = new RateLimitConfig( requests: 100, perSeconds: 60, retry: false, maxRetries: 3, backoff: 'exponential',);Concurrency Limiting
Section titled “Concurrency Limiting”public function concurrencyLimit(): ?int{ return 10; // Max 10 concurrent requests}Rate Limit Stores
Section titled “Rate Limit Stores”Memory Store (Default)
Section titled “Memory Store (Default)”use Cline\Relay\Features\RateLimiting\MemoryStore;
public function rateLimitStore(): RateLimitStore{ return new MemoryStore();}Cache Store
Section titled “Cache Store”use Cline\Relay\Features\RateLimiting\CacheStore;
public function rateLimitStore(): RateLimitStore{ return new CacheStore( cache: app('cache')->store('redis'), prefix: 'rate_limit', );}Request-Level Rate Limiting
Section titled “Request-Level Rate Limiting”use Cline\Relay\Support\Attributes\RateLimiting\RateLimit;use Cline\Relay\Support\Attributes\RateLimiting\ConcurrencyLimit;
#[Get]#[RateLimit(maxAttempts: 10, decaySeconds: 60)]#[ConcurrencyLimit(max: 5)]class SearchRequest extends Request{ public function endpoint(): string { return '/search'; }}Handling Rate Limit Exceptions
Section titled “Handling Rate Limit Exceptions”Client-Side Rate Limit
Section titled “Client-Side Rate Limit”use Cline\Relay\Support\Exceptions\Client\RateLimitException;
try { $response = $connector->send(new SearchRequest());} catch (RateLimitException $e) { if ($e->isClientSide()) { $retryAfter = $e->retryAfter(); $remaining = $e->remaining(); }}Server-Side Rate Limit (429)
Section titled “Server-Side Rate Limit (429)”try { $response = $connector->send(new SearchRequest());} catch (RateLimitException $e) { if ($e->isServerSide()) { $retryAfter = $e->retryAfter(); // From Retry-After header }}Reading Rate Limit Headers
Section titled “Reading Rate Limit Headers”$response = $connector->send(new GetUsersRequest());
$rateLimit = $response->rateLimit();
if ($rateLimit) { echo "Limit: {$rateLimit->limit}"; echo "Remaining: {$rateLimit->remaining}"; echo "Reset: {$rateLimit->reset}";}Rate Limit with Retry
Section titled “Rate Limit with Retry”#[Get]#[RateLimit(maxAttempts: 100, decaySeconds: 60)]#[Retry(times: 3, sleepMs: 1000, when: [429])]class SearchRequest extends Request {}Custom Backoff Strategy
Section titled “Custom Backoff Strategy”use Cline\Relay\Support\Contracts\BackoffStrategy;
class FibonacciBackoffStrategy implements BackoffStrategy{ public function calculateDelay(Request $request, int $attempt, int $retryAfter = 0): int { if ($retryAfter > 0) { return $retryAfter * 1_000; } return $this->fibonacci($attempt) * 1_000; }
private function fibonacci(int $n): int { if ($n <= 2) return 1; $a = 1; $b = 1; for ($i = 3; $i <= $n; $i++) { $c = $a + $b; $a = $b; $b = $c; } return $b; }}
#[RateLimit(requests: 100, perSeconds: 60, backoff: FibonacciBackoffStrategy::class)]class ApiRequest extends Request {}Rate Limit Buckets
Section titled “Rate Limit Buckets”Use different buckets for different endpoints:
#[Get]#[RateLimit(maxAttempts: 10, decaySeconds: 60, key: 'search')]class SearchRequest extends Request {}
#[Get]#[RateLimit(maxAttempts: 100, decaySeconds: 60, key: 'read')]class GetDataRequest extends Request {}Best Practices
Section titled “Best Practices”- Use persistent storage - CacheStore for distributed systems
- Set conservative limits - Stay below API limits
- Combine with retry - Auto-retry on 429 responses
- Monitor remaining quota - Check headers and log warnings