---
title: "Rate limiting — Plausible for Laravel | Roundly"
description: "Per-surface client-side rate limits for stats, events and sites, with adaptive 429 backoff, fail-fast and shared budgets."
url: https://roundly-consulting.com/open-source/docs/plausible-for-laravel/rate-limiting
language: en
---

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

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

# Rate limiting

Every outbound call is paced through http-client-rate-limits-for-laravel, so you proactively stay under Plausible’s quotas instead of hammering the API and eating 429s. There are three independent buckets — stats (query API), events (ingestion) and sites (sites, goals and shared links) — each keyed plausible:{surface}:{owner}:{host}\[:{site}\]. The host comes from your instance URL, so Cloud and self-hosted never share a window.

Plausible’s limit — 600 requests per hour by default — applies per API key, across every site the key can read. So stats and sites default to one account-wide bucket (per\_site off): a dashboard over 20 sites still spends one 600/hour budget, as Plausible counts it. events is unauthenticated and keeps a bucket per site.

## Bucket settings

Configure each bucket under plausible.rate\_limits.{surface}:

| Key | Stats | Events | Sites | Purpose |
| --- | --- | --- | --- | --- |
| `enabled` | true | true | true | false runs the plain client with no limiter. |
| `owner` | app | app | app | Folds several app instances sharing one API key into one window. |
| `limit` | 600 | 6000 | 60 | Max requests per window. |
| `per` | hour | minute | hour | Window size: second, minute, hour or day. |
| `adaptive` | true | true | true | Honour Plausible’s 429 Retry-After for server-authoritative backoff. |
| `max_wait` | null | null | null | Milliseconds. null = pace (wait); set it to fail fast with RateLimitExceeded. |
| `jitter` | null | null | null | Milliseconds of random spread added to defers. |
| `per_site` | false | true | false | Include the site in the key — a separate budget per site. Keep it off for stats and sites unless your key really has per-site quotas. |

Stats defaults to Plausible’s documented 600 requests per hour per key; Events is a generous per-minute budget for high-volume ingestion; Sites is a low provisioning budget drawn from the same key. Self-hosted instances may set different numbers.

## Environment variables

Each key is backed by an env variable. The stats names are shown — replace STATS with EVENTS or SITES for the other buckets. The boolean keys — enabled, adaptive and per\_site — accept true/false, on/off, yes/no and 1/0; anything else throws an InvalidConfigurationException naming the key. The rest are just as strict: per must be one of second, minute, hour or day (a typo such as hourly throws instead of becoming hourly), limit is a whole number of at least 1, max\_wait and jitter are whole numbers of at least 0 (or unset), and owner is a string. A blank value (PLAUSIBLE\_STATS\_RATELIMIT=) is not set, so its default applies:

| Key | Env variable |
| --- | --- |
| `enabled` | PLAUSIBLE\_STATS\_RATELIMIT\_ENABLED |
| `owner` | PLAUSIBLE\_RATELIMIT\_OWNER (shared by all three) |
| `limit` | PLAUSIBLE\_STATS\_RATELIMIT |
| `per` | PLAUSIBLE\_STATS\_RATELIMIT\_PER |
| `adaptive` | PLAUSIBLE\_STATS\_RATELIMIT\_ADAPTIVE |
| `max_wait` | PLAUSIBLE\_STATS\_RATELIMIT\_MAX\_WAIT |
| `jitter` | PLAUSIBLE\_STATS\_RATELIMIT\_JITTER |
| `per_site` | PLAUSIBLE\_STATS\_RATELIMIT\_PER\_SITE |

```bash
PLAUSIBLE_RATELIMIT_OWNER=app
PLAUSIBLE_STATS_RATELIMIT=600
PLAUSIBLE_STATS_RATELIMIT_PER=hour
PLAUSIBLE_STATS_RATELIMIT_MAX_WAIT=5000
PLAUSIBLE_EVENTS_RATELIMIT_PER=minute
PLAUSIBLE_SITES_RATELIMIT_ENABLED=false

# Booleans accept true/false, on/off, yes/no and 1/0; anything else throws.
# Only for a key with real per-site quotas:
PLAUSIBLE_STATS_RATELIMIT_PER_SITE=on
```

## Wait vs fail fast

By default a request that would exceed its window waits until the window frees up. Set max\_wait (ms) to instead throw RoundlyConsulting\\Plausible\\Exceptions\\RateLimitExceeded, carrying the budget key and the wait window via retryAfterSeconds():

```php
use RoundlyConsulting\Plausible\Exceptions\RateLimitExceeded;

try {
    plausible()->stats()->aggregateResult($request);
} catch (RateLimitExceeded $e) {
    report("Retry {$e->key} in {$e->retryAfterSeconds()}s");
}
```

Because the hint rides on RoundlyConsulting\\PackageToolkit\\Contracts\\HasRetryAfter, a host can answer any rate-limited failure — from this package or another Roundly package — with one check:

```php
use RoundlyConsulting\PackageToolkit\Contracts\HasRetryAfter;

if ($e instanceof HasRetryAfter) {
    return response('Too Many Requests', 429, ['Retry-After' => $e->retryAfterSeconds()]);
}
```

## Retries and shared budgets

- Native retry — retry.\* (off by default) covers transient failures only: a failed connection or a 5xx. A 4xx is final, so a 429 goes straight to the limiter, which reads its Retry-After (adaptive) and makes the next call wait; the attempts of one retried call share one limiter slot. An event whose request timed out is never retried — Plausible may already have recorded it, so a retry could count it and its revenue twice.
- Shared budgets — the limiter defaults to an in-memory, per-process store: fine for a single worker, CLI or scheduled report. For a budget shared across workers or servers, point its store at Cache, Redis or Database via HTTP\_CLIENT\_RATE\_LIMITS\_STORE (see http-client-rate-limits-for-laravel).
- Per-site quotas — per\_site is off for stats and sites because Plausible counts requests per API key; turn it on only if your key really has per-site quotas.

## 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/plausible-for-laravel.md)
