NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages

The RateLimits facade is the package’s one entry point for building limits. It resolves the container-bound RateLimitManager singleton, so the configured store and deferrer — or a RateLimits::fake() — apply to every limit, including the ones Http::rateLimit() builds:

use Illuminate\Support\Facades\Http;
use RoundlyConsulting\HttpClientRateLimits\Facades\RateLimits;

// Build a limit and attach it to a request.
Http::rateLimit(RateLimits::perSecond(5)->by('acct-1'))->get('https://api.example.com/orders');

// Profiles and compound windows come from the same place.
$github = RateLimits::profile('github');
$api    = RateLimits::compound([RateLimits::perSecond(5), RateLimits::perMinute(100)]);

// Inspect or clear a budget without sending anything.
$limit = RateLimits::perMinute(30)->by('acct-1');
$limit->remaining();
$limit->reset();

The full surface

use RoundlyConsulting\HttpClientRateLimits\Facades\RateLimits;

RateLimits::make(Limit $limit);                  // from a Limit value object
RateLimits::perSecond(int $maxAttempts = 1);     // N requests per second
RateLimits::perMinute(int $maxAttempts = 1);     // N requests per minute
RateLimits::perHour(int $maxAttempts = 1);       // N requests per hour
RateLimits::perDay(int $maxAttempts = 1);        // N requests per day
RateLimits::profile(string $name);               // a named profile from config
RateLimits::compound(array $limits);             // several windows at once
RateLimits::usingStore(Store $store);            // a copy of the manager on this store
RateLimits::usingDeferrer(Deferrer $deferrer);   // … or with this deferrer
RateLimits::releasingJob(object $job);           // … releasing a queued job instead of sleeping
RateLimits::retryAfter(Response|RequestException $response); // ?int seconds from Retry-After
RateLimits::store();                             // the shared default store
RateLimits::deferrer();                          // the default deferrer
MethodReturnsPurpose
make(Limit $limit)RateLimitBuild from a Limit value object, on the manager’s store and deferrer.
perSecond / perMinute / perHour / perDay(int $maxAttempts = 1)RateLimitN requests per window.
profile(string $name)RateLimitA named profile from the limiters config; unknown names throw UnknownLimiterProfileException.
compound(array $limits)RateLimitSeveral windows at once; the first is primary, the strictest wins.
usingStore(Store $store)RateLimitManagerA copy of the manager on this store; the singleton is untouched.
usingDeferrer(Deferrer $deferrer)RateLimitManagerA copy of the manager with this deferrer.
releasingJob(object $job)RateLimitManagerA copy that releases the queued job instead of sleeping (the ReleaseDeferrer).
retryAfter(Response|RequestException $response)?intSeconds from a Retry-After header (delta or HTTP-date); null when absent or unparseable.
store()StoreThe shared store new limits record hits in.
deferrer()DeferrerThe deferrer new limits wait with.
fake()RateLimitsFakeSwap in the recording test fake.

What a limit can do

Every factory returns a RateLimit — a Guzzle middleware you pass to Http::rateLimit() or Http::withMiddleware(), or wrap around any callable with handle(). It is fluent, and anything it doesn’t define itself is forwarded to the underlying Limit, then the Limiter:

GroupMethods on the returned RateLimit
Configure (fluent)by(), alongside(), maxWait(), jitter(), adaptive(), setStore(), setDeferrer()
Inspectremaining(), availableIn(), tooManyAttempts(), delayUntilNextRequestInMs(), getKey(), getMaxAttempts(), getTimespan(), isOverMaxAttempts(), isUnderMaxAttempts(), getStore(), getDeferrer(), getLimiter()
Clearreset()
Runhandle(callable)

by() scopes every window the limit enforces; alongside() and compound() take copies of the limits you hand them, so re-keying the result never changes those.

Per-call overrides

Override the store or deferrer for one call site without touching the global defaults:

use RoundlyConsulting\HttpClientRateLimits\Deferrer\SleepDeferrer;
use RoundlyConsulting\HttpClientRateLimits\Facades\RateLimits;
use RoundlyConsulting\HttpClientRateLimits\Store\RedisStore;

// usingStore() / usingDeferrer() return a configured copy; the shared manager is untouched.
$limit = RateLimits::usingStore(new RedisStore('default'))
    ->usingDeferrer(new SleepDeferrer)
    ->perMinute(30);

Prefer an explicit dependency over a static call? See DI and actions — the injected manager has the same API.

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 crypto

By 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.