Skip to content

feat(throttle): admin-managed rate limits, org overrides and API consumer metrics - #281

Merged
roncodes merged 2 commits into
release/v1.6.66from
feature/rate-limit-admin
Sep 29, 2026
Merged

roncodes merged 2 commits into
release/v1.6.66from
feature/rate-limit-admin

Conversation

@roncodes

Copy link
Copy Markdown
Member

Stacked on #280 (per-consumer throttle keying). Merge that first; this PR then retargets to main.

What

  • ApiRateLimits: the effective rate-limit settings.
    • The environment (THROTTLE_*) supplies the defaults. A system admin can override them at runtime; the override is stored as system.rate-limits, cached, and cleared on save.
    • Per-organization overrides: a custom limit, or unlimited.
    • It resolves which organization a credential belongs to without authenticating it:
      • API keys (live, flb_test_ sandbox, and $ secrets, which are retried on sandbox)
      • Sanctum tokens
      • The result is cached for 10 minutes per credential, and unknown credentials are cached too.
  • ApiConsumerMetrics: per-consumer request and 429 counts.
    • Stored in Redis sorted sets: minute buckets kept for 2 hours, hour buckets for 8 days. Writing them costs one pipelined round trip per request.
    • Best-effort: if Redis is unavailable nothing is recorded, and the request never fails because of it.
    • Needed because api_request_logs only records writes and never sees a 429.
  • ThrottleRequests applies the settings and overrides, and records every request, including throttled ones and those that bypass throttling.
  • RateLimitController (behind AdminRequest), under int/v1/rate-limits:
    • GET/POST/DELETE settings
    • GET consumers?window=&sort=&limit=
    • POST consumers/{signature}/reset, which clears one consumer's limiter window
  • Config: THROTTLE_TRACK_CONSUMERS (default true) and THROTTLE_METRICS_REDIS_CONNECTION (default cache).

The console UI is in the companion fleetbase/fleetbase PR.

Tests

  • 40 new tests plus a routes-contract assertion.
  • 100% line coverage on the four new or changed source files (measured under host PHP 8.4 with Xdebug).
  • Full suite: 1920 passed. test:lint and test:date-drift are clean.
  • Checked end to end in the dev stack:
    • an org override applied to a real key
    • an unknown key throttled at the default limit
    • both consumers listed with organization, masked key, scope and IP

…y IP

ThrottleRequests runs before API authentication, so Laravel's default
signature always fell back to route domain + client IP. Behind a load
balancer that IP is the balancer's, and no route has a domain, so every
tenant, API key and console visitor shared one bucket: one busy
integration returned 429 to the whole platform.

Key the limiter on the hashed credential (API key, Sanctum token, basic
auth), falling back to the user and then the IP only when none is sent,
and scope it by first path segment so /v1 and /int stay separate.

Also keep Retry-After and X-RateLimit-* on the 429 response so throttled
clients know when to retry.
…umer metrics

- ApiRateLimits: effective limits from env defaults plus a system.rate-limits
  setting an admin can change at runtime; per-organization overrides (custom
  limit or unlimited); credential -> organization lookup cached per key.
- ApiConsumerMetrics: per-consumer request and 429 counts in Redis
  (minute buckets for 2h, hour buckets for 8 days), recorded by the
  throttle middleware so throttled requests are visible too.
- RateLimitController + int/v1/rate-limits routes (admin only): get/save/reset
  settings, top consumers by window, clear a consumer's limiter window.
- ThrottleRequests applies overrides and records every request.
@roncodes roncodes mentioned this pull request Sep 29, 2026
@roncodes
roncodes changed the base branch from fix/per-consumer-throttle to release/v1.6.66 September 29, 2026 08:27
@roncodes
roncodes merged commit 057cfd0 into release/v1.6.66 Sep 29, 2026
@roncodes
roncodes deleted the feature/rate-limit-admin branch September 29, 2026 09:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant