---
title: "Adaptive limiting — HTTP Client Rate Limits for Laravel | Roundly"
description: "Honour the server’s own budget — read Retry-After and X-RateLimit-* headers and back off exactly as long as asked."
url: https://roundly-consulting.com/open-source/docs/http-client-rate-limits-for-laravel/adaptive-limiting
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[HTTP Client Rate Limits for Laravel](https://roundly-consulting.com/open-source/docs/http-client-rate-limits-for-laravel.md)

# Adaptive limiting

Opt in with ->adaptive() and the limiter reads Retry-After and X-RateLimit-Remaining / X-RateLimit-Reset from each response, recording a penalty in the store so the next request waits exactly as long as the server asked:

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

Http::rateLimit(RateLimits::perSecond(20)->adaptive())
    ->get('https://api.example.com/things');
```

It works the same through Http::rateLimit(), Http::withMiddleware(), Http::pool(), an adaptive named profile and handle() around any callable that returns a response — or throws a RequestException (->throw() on a 429): the limiter adapts from that exception’s response, then rethrows it:

```php
$api = RateLimits::perSecond(20)->by('api')->adaptive();

// Wrap any callable that returns a response; its headers are read the same way.
$response = $api->handle(fn () => Http::get('https://api.example.com/things'));

// After a 429 with "Retry-After: 7", the next call through $api waits 7 seconds.
$next = $api->handle(fn () => Http::get('https://api.example.com/things'));
```

## How the headers are read

- Retry-After — delta-seconds or an HTTP-date; it takes precedence when present.
- Otherwise, X-RateLimit-Remaining: 0 together with a numeric X-RateLimit-Reset — whole or fractional seconds (1790000030.250), rounded up. The reset may be a delay in seconds or an epoch timestamp; an epoch that has already passed asks for no wait.
- Headers that don’t ask for a backoff — remaining above zero, a missing or non-numeric reset (negative, 1e3, …) — are ignored.
- A server wait is honoured up to one day: a larger Retry-After / X-RateLimit-Reset (hostile or broken) is capped there rather than stalling the worker or overflowing.
- The penalty is stored with the store’s penalizeUntil() under every adaptive window’s owner key. On a shared store, every process using that key backs off together.

## From a named profile

```php
// config/http-client-rate-limits.php
'limiters' => [
    'partner' => ['rate' => 20, 'per' => 'second', 'by' => 'partner', 'adaptive' => true],
],

// anywhere
Http::rateLimit('partner')->get('https://partner.example.com/feed');
```

Adaptive limiting complements the proactive pacing — the declared rate still applies, and the server’s signal extends the wait when it asks for more.

## 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](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

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

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/http-client-rate-limits-for-laravel.md)
