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

Define reusable limits once in config/http-client-rate-limits.php and reference them by name from anywhere:

// config/http-client-rate-limits.php
'limiters' => [
    'github' => ['rate' => 5, 'per' => 'second'],
    'billing' => ['rate' => 100, 'per' => 'minute', 'by' => 'tenant-1', 'jitter' => 50],
],
use RoundlyConsulting\HttpClientRateLimits\Facades\RateLimits;

Http::rateLimit('github')->get('https://api.github.com/user');

// Re-key a profile per owner at the call site:
Http::rateLimit('github', by: 'acct-1')->get('https://api.github.com/user');

// The same profile outside the HTTP client:
$limit = RateLimits::profile('github');
$limit->remaining();

Referencing a name that isn’t defined throws UnknownLimiterProfileException.

Profile keys

Each profile array accepts rate and per, plus the optional by, trim, max_wait, jitter and adaptive keys. A key that is not set — omitted, null or blank ('', whitespace) — falls back to these defaults:

KeyDefaultPurpose
rate1Requests allowed in the window.
perminuteWindow: second, minute, hour or day.
bynull (global)Owner key the budget is tracked under.
trimfalseDrop the owner’s hits older than the window after each allowed request.
max_waitnullMax wait in ms; a longer wait throws RateLimitExceededException.
jitter0Up to this many ms of random extra wait — never shortens a wait.
adaptivefalseSelf-tune from Retry-After / X-RateLimit-* response headers.

A profile using every key:

'limiters' => [
    'openai' => [
        'rate' => 60,          // requests allowed in the window
        'per' => 'minute',     // second | minute | hour | day
        'by' => 'openai',      // owner key
        'trim' => true,        // drop hits older than the window after each request
        'max_wait' => 10_000,  // ms: throw RateLimitExceededException above this wait
        'jitter' => 100,       // ms: up to this much random extra wait
        'adaptive' => true,    // honour Retry-After / X-RateLimit-* response headers
    ],
],

Strict values

Every profile value is read strictly when the profile is resolved, never guessed at:

  • rate, max_wait and jitter take an integer or an integer string ('5'); 'five', '5.5' or '1e3' throws InvalidConfigurationException, and so does a negative max_wait or jitter. A rate below 1 throws InvalidLimitException.
  • per takes second, minute, hour or day (exact) or a Timespan; a typo such as minutes, or an unsupported week, throws InvalidTimespanException.
  • by takes a string; a non-string throws InvalidConfigurationException.
  • trim and adaptive take true/false, 1/0, on/off or yes/no; anything else throws InvalidConfigurationException.

Every error names the full key, e.g. http-client-rate-limits.limiters.github.adaptive. A key that is not set uses its default from the table above — so a blank max_wait means no ceiling, never a 0 ms fail-fast one.

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.