Skip to main content
Rate limiting in Apivalk is two pieces:
  • RateLimitMiddleware (global) — checks the hit counter in a cache and, when a rate limit is attached to the current route, either lets the request through or returns 429 Too Many Requests.
  • RateLimitInterface (per route) — the policy object on the Route. Ships with IpRateLimit; bring your own for anything else.

1. Register the middleware once

The middleware needs a cache to store counters. Any CacheInterface works — it can be the same one you use for the router index.
Add it before authentication in the stack if you want unauthenticated clients also throttled before key validation runs.

2. Attach a limit to a specific route

IpRateLimit throttles by client IP. The constructor is (string $name, int $maxAttempts, int $windowInSeconds).
Routes without rateLimit(...) bypass the limiter entirely — RateLimitMiddleware no-ops them. Only routes with a policy attached ever land in the cache.

3. What happens at request time

On every hit, RateLimiter::allow() increments a counter keyed by getKey(RateLimitContext). If the incremented value exceeds getMaxAttempts() within the window, RateLimitMiddleware short-circuits with TooManyRequestsApivalkResponse (HTTP 429). Either way, the RateLimitResult is set on the request and the MiddlewareStack appends these headers to every response for the route: The OpenAPI generator documents these headers automatically for any route with a rate limit.

4. Custom strategies

Implement RateLimitInterface to key on anything other than IP — API key, user id, tenant.
RateLimitContext is built per-request; it exposes the route and the request, so any identifier you can get from those is fair game for the cache key. Order matters: the route’s rateLimit(...) must be called after whatever middleware populates that identifier — AuthenticationMiddleware for auth-keyed limits.

5. Stacking limits

Only one rateLimit() per route. If you need “5 req/s + 1000 req/day”, implement a composite RateLimitInterface that owns two counters internally and returns the stricter one.

6. Testing locally

Because FilesystemCache persists counters to disk, restart behaviour is: counters survive PHP restarts but disappear when you clear the cache directory. Clearing it is the fastest way to reset a limit during development.

Reference

Routing / Rate Limit walks through the RateLimitResult shape and header serialisation. Middleware / Rate Limit covers middleware-level concerns.