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

Every exception lives in RoundlyConsulting\HttpClientRateLimits\Exceptions and extends RateLimitException:

ExceptionThrown when
RateLimitExceptionBase class (extends RuntimeException) — catch it to handle the whole family below.
RateLimitExceededExceptionA computed wait exceeds the maxWait() ceiling (the tightest one across a compound limit’s windows). Exposes key, delayMs and maxWaitMs.
JobReleasedExceptionThe ReleaseDeferrer has released the queued job. Exposes delaySeconds, key and job; the HandlesRateLimitRelease job middleware ends the attempt cleanly.
InvalidLimitExceptionA limit allows fewer than one request per window (a rate or maxAttempts below 1), or Http::rateLimit() gets an empty array or an array entry that isn’t a Limit, a RateLimit, a profile name or an integer.
UnknownLimiterProfileExceptionA profile name isn’t defined under limiters (or its entry there isn’t an array).
InvalidStoreExceptionThe configured store doesn’t implement the Store contract.
InvalidDeferrerExceptionThe configured deferrer doesn’t implement Deferrer, or releasingJob() / ReleaseDeferrer gets a job without release().
InvalidTimespanExceptionA timespan string — also a profile’s per — isn’t one of second, minute, hour, day.
UndefinedMethodExceptionA method called on RateLimit exists on neither Limit nor Limiter.

Catching everything

use RoundlyConsulting\HttpClientRateLimits\Exceptions\RateLimitException;

try {
    Http::rateLimit('github')->get('https://api.github.com/user');
} catch (RateLimitException $e) {
    // Any package error: unknown profile, max wait exceeded, invalid store/deferrer, …
    report($e);
}

In a queued job using the ReleaseDeferrer, a catch-all also swallows JobReleasedException — and the job is already back on the queue, so the attempt carries on as if the request had run while the re-queued copy runs again later. Catch narrower, or rethrow the release first and let HandlesRateLimitRelease end the attempt:

use RoundlyConsulting\HttpClientRateLimits\Exceptions\JobReleasedException;
use RoundlyConsulting\HttpClientRateLimits\Exceptions\RateLimitException;

try {
    Http::withMiddleware(RateLimits::releasingJob($this)->perMinute(60)->by('github'))->get($url);
} catch (JobReleasedException $released) {
    throw $released; // HandlesRateLimitRelease ends the attempt
} catch (RateLimitException $e) {
    // a real limit or setup failure
}

Some errors from the same calls are not RateLimitExceptions: package-toolkit’s InvalidConfigurationException for a malformed config value (see Configuration), Laravel’s LockTimeoutException when the CacheStore can’t take a window lock in time, a RequestException rethrown from a handle() callback that calls ->throw(), and a store backend’s own failures — Redis connection errors, database QueryExceptions — which pass through unwrapped.

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.