Pre-flight inspection
Ask the limiter about the current state before sending — without recording a hit:
use RoundlyConsulting\HttpClientRateLimits\Facades\RateLimits;
$limit = RateLimits::perMinute(30)->by('acct-1');
$limit->remaining(); // requests still allowed in the window (0 during a server penalty)
$limit->availableIn(); // ms until the next request is allowed (0 = now)
$limit->tooManyAttempts(); // bool: is the window exhausted right now?Delay at a given timestamp
RateLimit forwards calls to the underlying Limit and Limiter, so you can ask how long you’d have to wait at a specific moment (in milliseconds, from the deferrer’s clock):
$middleware = RateLimits::perHour(6);
$at = $middleware->getDeferrer()->timestamp();
$delay = $middleware->delayUntilNextRequestInMs($at); // 0 = send now, else wait this many ms
$middleware->isOverMaxAttempts(7); // trueGetters
$limit = RateLimits::perMinute(30)->by('acct-1');
$limit->getKey(); // 'acct-1'
$limit->getMaxAttempts(); // 30
$limit->getTimespan(); // 'minute'
$limit->isUnderMaxAttempts(29); // true
$limit->getStore(); // the Store in use
$limit->getDeferrer(); // the Deferrer in use
$limit->getLimiter(); // the underlying LimiterRescheduling instead of waiting
Inspection reads the store, so in queued jobs pair it with a shared store. A job can then release itself instead of waiting:
public function handle(): void
{
// Needs a shared store (cache, Redis or database) so every run reads the same budget.
$limit = RateLimits::profile('github')->by("acct:{$this->accountId}");
if ($limit->tooManyAttempts()) {
$this->release((int) ceil($limit->availableIn() / 1000));
return;
}
Http::withMiddleware($limit)->get('https://api.github.com/user');
}Inspection methods describe the primary window (plus any adaptive penalty on it) and record nothing; compound windows are evaluated when the request is sent.
Resetting a limit
Clear the hits a key has recorded — after a plan upgrade, or between two phases of a batch — so the next request goes straight through:
use RoundlyConsulting\HttpClientRateLimits\Facades\RateLimits;
RateLimits::perMinute(60)->by('stripe')->reset();
// A compound limit clears every window it enforces.
RateLimits::perSecond(5)->by('stripe')->alongside(RateLimits::perMinute(100)->by('stripe'))->reset();A reset clears the windows the limit enforces (stripe:minute in the first line), not other windows on the same key, and it doesn’t lift a penalty an adaptive limit recorded from the server’s own Retry-After — the server asked for that wait. It fires RateLimitReset.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.