---
title: "Rate limiting — Geolocation for Laravel | Roundly"
description: "Pace every HTTP provider under its own budget with adaptive Retry-After backoff, or fail fast with a typed exception past a ceiling."
url: https://roundly-consulting.com/open-source/docs/geolocation-for-laravel/rate-limiting
language: en
---

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

[Geolocation for Laravel](https://roundly-consulting.com/open-source/docs/geolocation-for-laravel.md)

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

```php
'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. |

```bash
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 s
```

## Pace 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:

```php
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. A distance matrix split into several requests takes one slot per request.

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

```php
// 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 failures only: a connection error or a 5xx is retried up to services.<provider>.retry times, while a 4xx — a bad key, an unknown IP, a 429 — is returned at once and never re-sent. All attempts of one call share one slot of the budget; 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 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/geolocation-for-laravel.md)
