API Rate Limiting
As of OXID eShop version 7.5, the API includes built-in rate limiting to protect against abuse and ensure fair resource usage.
Rate limiting applies to requests handled by the API entrypoint
(paths starting with /api/). Other shop traffic is not affected.
Rate limiting restricts the number of API requests a client can make within a specified time window, helping to prevent denial-of-service attacks and ensuring service availability.
Overview
The rate limiter provides:
Token bucket algorithm for smooth rate limiting
Sliding window algorithm as an alternative
Per-client tracking based on IP address
Configurable limits, intervals, and excluded routes
Standard rate limit headers in responses
Configuration
Configure rate limiting in your project’s services.yaml by overriding the parameters:
Example
parameters:
oxid_esales.rate_limiter.enabled: true
oxid_esales.rate_limiter.limit: 100
oxid_esales.rate_limiter.interval: 60
oxid_esales.rate_limiter.policy: 'token_bucket'
oxid_esales.rate_limiter.excluded_routes: []
Parameters
oxid_esales.rate_limiter.enabledEnable or disable rate limiting. Default:
trueoxid_esales.rate_limiter.limitMaximum number of requests allowed per interval. Default:
100oxid_esales.rate_limiter.intervalTime window in seconds. Default:
60oxid_esales.rate_limiter.policyRate limiting algorithm. Options:
token_bucket,sliding_window. Default:token_bucketoxid_esales.rate_limiter.excluded_routesArray of route patterns to exclude from rate limiting. Default:
[]
Excluding Routes
You can exclude specific routes from rate limiting using exact paths or wildcard patterns:
Example
parameters:
oxid_esales.rate_limiter.excluded_routes:
- '/api/health'
- '/api/public/*'
Pattern Matching
Route matching supports exact paths and wildcard patterns using *:
*matches any number of characters
Examples:
/api/health- exact match/api/public/*- matches/api/public/info,/api/public/status, etc.
Response Headers
Successful API responses include rate limit information headers:
X-RateLimit-LimitMaximum requests allowed per interval.
X-RateLimit-RemainingRemaining requests in current window.
X-RateLimit-ResetUnix timestamp when the rate limit resets.
Example
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1699123456
Rate Limit Exceeded
When a client exceeds the rate limit, the API returns a 429 Too Many Requests response:
Example
{
"error": "rate_limit_exceeded",
"message": "Too many requests. Please try again later.",
"retry_after": 45
}
The response includes these headers:
Retry-AfterSeconds until the client can retry.
X-RateLimit-ResetUnix timestamp when the rate limit resets.
Disabling Rate Limiting
To disable rate limiting entirely:
parameters:
oxid_esales.rate_limiter.enabled: false
Warning
Disabling rate limiting in production environments is not recommended as it exposes your API to potential abuse.
Best Practices
- Set appropriate limits
Consider your expected traffic and server capacity. Start conservative and adjust based on monitoring.
- Exclude health checks
Always exclude health check endpoints used by load balancers:
parameters: oxid_esales.rate_limiter.excluded_routes: - '/api/health' - '/api/ping'
- Monitor rate limit hits
Track 429 responses in your monitoring to identify potential issues or attacks.
- Communicate limits
Document your rate limits for API consumers so they can implement appropriate retry logic.
Client Implementation
API clients should handle rate limiting gracefully:
Example
$response = $httpClient->request('GET', '/api/products');
if ($response->getStatusCode() === 429) {
$retryAfter = $response->getHeaderLine('Retry-After');
sleep((int) $retryAfter);
// Retry the request
}
Customization
Custom Client Identifier
By default, clients are identified by IP address. To customize this, create a service
implementing ClientIdentifierProviderInterface:
Example
<?php
declare(strict_types=1);
namespace MyVendor\MyModule\RateLimiter;
use OxidEsales\EshopCommunity\Internal\Framework\RateLimiter\ClientIdentifierProviderInterface;
use Symfony\Component\HttpFoundation\Request;
class ApiKeyClientIdentifier implements ClientIdentifierProviderInterface
{
public function getClientIdentifier(Request $request): string
{
// Use API key as identifier if present, otherwise fall back to IP
return $request->headers->get('X-API-Key')
?? $request->getClientIp()
?? 'unknown';
}
}
Register your implementation in services.yaml:
OxidEsales\EshopCommunity\Internal\Framework\RateLimiter\ClientIdentifierProviderInterface:
class: MyVendor\MyModule\RateLimiter\ApiKeyClientIdentifier