Rate limiting
Each HTTP provider — google, ipinfo, ip2location and maxmind_web — sends through http-client-rate-limits-for-laravel, so calls are paced before they hit a third-party API instead of burning through 429s. Every provider has its own budget, keyed geolocation:{provider}:{owner}. The offline maxmind_database and default providers make no network calls and are never throttled.
Default budgets
| Provider | limit / per | Env |
|---|---|---|
google | 50 / second | GEOLOCATION_GOOGLE_RATELIMIT, …_PER |
ipinfo | 60 / minute | GEOLOCATION_IPINFO_RATELIMIT, …_PER |
ip2location | 60 / minute | GEOLOCATION_IP2LOCATION_RATELIMIT, …_PER |
maxmind_web | 60 / minute | GEOLOCATION_MAXMIND_WEB_RATELIMIT, …_PER |
All budgets are adaptive and pace by default.
Per-provider keys
Each HTTP provider carries a rate_limits block under services.<provider>:
'ipinfo' => [
// ...
'rate_limits' => [
'enabled' => env('GEOLOCATION_IPINFO_RATELIMIT_ENABLED', true),
'owner' => env('GEOLOCATION_RATELIMIT_OWNER', 'app'),
'limit' => env('GEOLOCATION_IPINFO_RATELIMIT', 60),
'per' => env('GEOLOCATION_IPINFO_RATELIMIT_PER', 'minute'), // second|minute|hour|day
'adaptive' => env('GEOLOCATION_IPINFO_RATELIMIT_ADAPTIVE', true),
'max_wait' => env('GEOLOCATION_IPINFO_RATELIMIT_MAX_WAIT'), // ms; null = pace, set = fail fast
'jitter' => env('GEOLOCATION_IPINFO_RATELIMIT_JITTER'), // ms; null = none
],
],| Key | Default | Env (google shown) | Purpose |
|---|---|---|---|
enabled | true | GEOLOCATION_GOOGLE_RATELIMIT_ENABLED | false sends with a plain, unthrottled client. |
owner | app | GEOLOCATION_RATELIMIT_OWNER | Owner segment of the budget key (shared by all providers). A blank value is not set (the default); a non-string value throws. |
limit | 50 (google) / 60 | GEOLOCATION_GOOGLE_RATELIMIT | Max requests per window, at least 1. |
per | second (google) / minute | GEOLOCATION_GOOGLE_RATELIMIT_PER | Window: second, minute, hour or day; a blank value is not set (the default); anything else throws InvalidConfigurationException. |
adaptive | true | GEOLOCATION_GOOGLE_RATELIMIT_ADAPTIVE | Honour the provider’s 429 Retry-After. |
max_wait | null | GEOLOCATION_GOOGLE_RATELIMIT_MAX_WAIT | Fail-fast ceiling in ms (0 or more); null or blank = pace (wait). |
jitter | null | GEOLOCATION_GOOGLE_RATELIMIT_JITTER | Random spread in ms added to deferrals (0 or more); null or blank adds none. |
GEOLOCATION_RATELIMIT_OWNER=app
GEOLOCATION_GOOGLE_RATELIMIT=50
GEOLOCATION_GOOGLE_RATELIMIT_PER=second
GEOLOCATION_IPINFO_RATELIMIT=60
GEOLOCATION_IPINFO_RATELIMIT_PER=minute
GEOLOCATION_IPINFO_RATELIMIT_MAX_WAIT=2000 # fail fast instead of waiting more than 2 sPace or fail fast
By default the limiter waits until the window frees, then sends. Set max_wait (milliseconds) to fail fast instead: when a deferral would exceed it, RateLimitExceededException is thrown. It extends GeolocationException, carries ->provider, and implements the toolkit’s HasRetryAfter contract:
use RoundlyConsulting\Geolocation\Exceptions\RateLimitExceededException;
try {
$location = Geolocation::locateIp($request->ip());
} catch (RateLimitExceededException $e) {
$e->provider; // e.g. 'ipinfo'
return response('Location lookup is busy, please retry.', 429)
->header('Retry-After', (string) $e->retryAfterSeconds());
}With adaptive on, a provider’s 429 Retry-After tunes the limiter for the next call, while the failed response itself still degrades to null so the pipeline moves on.
Bulk work
batch() and distanceMatrix() run through the same limiter, so bulk lookups pace themselves under the provider’s budget automatically.
Shared budgets across workers
The rate-limit package uses an in-memory store by default — one budget per process, fine for a single worker or CLI run. For an account quota shared across workers and servers, point it at a shared store in its own config (HTTP_CLIENT_RATE_LIMITS_STORE); this package does not force one:
// config/http-client-rate-limits.php — one budget across every worker and server
'store' => \RoundlyConsulting\HttpClientRateLimits\Store\RedisStore::class,Laravel’s native ->retry(times, delay) still applies for transient connection errors — the rate limiter is an extra, outgoing pacing layer, not a replacement.
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 cryptoBy 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.