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

The facade is the shortest route and the recommended default, not the only one. The same API is available as RoundlyConsulting\HttpClientRateLimits\RateLimitManager — the facade root, bound as a singleton — which you can inject through the constructor: an explicit dependency and no static calls.

There is no third, action-based entry point: the package has no action classes, because a limit is a middleware object the manager builds, not a use case. So there is no facade method → action table either.

Inject the manager

use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
use RoundlyConsulting\HttpClientRateLimits\RateLimitManager;

final class OrdersClient
{
    public function __construct(private RateLimitManager $rateLimits) {}

    public function orders(string $account): Response
    {
        return Http::withMiddleware($this->rateLimits->perSecond(5)->by($account))
            ->get('https://api.example.com/orders');
    }
}
// Every facade method exists on the injected manager:
$limit   = $this->rateLimits->profile('github');
$seconds = $this->rateLimits->retryAfter($response);
$queued  = $this->rateLimits->releasingJob($job)->perSecond(5);

RateLimits::fake() returns a RateLimitsFake — a RateLimitManager subtype — and swaps it into the container, so an injected manager and Http::rateLimit() receive the fake as well.

Build a limit by hand

When you need full control, compose a RateLimit from a Limit, a Store and a Deferrer yourself:

use RoundlyConsulting\HttpClientRateLimits\Deferrer\SleepDeferrer;
use RoundlyConsulting\HttpClientRateLimits\Limit;
use RoundlyConsulting\HttpClientRateLimits\Limiter;
use RoundlyConsulting\HttpClientRateLimits\RateLimit;
use RoundlyConsulting\HttpClientRateLimits\Store\RedisStore;

$rateLimit = new RateLimit(new Limiter(
    new Limit(key: 'acct-1', maxAttempts: 5, timespan: 'second'),
    new RedisStore('default'),
    new SleepDeferrer,
));

Http::withMiddleware($rateLimit)->get('https://api.example.com/things');

A hand-built limit uses exactly the store and deferrer you pass: the configured defaults don’t apply to it, and neither do the fake’s shared store and non-sleeping deferrer.

Internal classes

RetryAfter is @internal — parse the header with RateLimits::retryAfter() (or $manager->retryAfter()) instead.

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.