Laravel rate limiting,with API keys your customers own.
Add the reqkey middleware to a route group. Every request is checked against the caller’s key, charged against their credits, and held to their plan’s limit.
- Keys issued per customer
- Credits that refill
- Limits per plan, not per process
Official SDKs with drop-in middleware for the stack you already run
- Python
- Node.js
- Go
- Rust
- PHP
- .NET
- Java
- API requests validated
- 100M+
- Average key validation
- <5ms
- Average analytics ingest
- <5ms
- Check and log, end to end
- <10ms
The usual Laravel rate limiter counts requests. It doesn’t know your customers.
Laravel’s RateLimiter facade defines named limiters — per user or per IP — and the throttle middleware applies them, with counts in your cache store.
// AppServiceProvider.php — Laravel’s RateLimiteruse Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
// routes/api.php
Route::middleware('throttle:api')->group(function () {
// ...
});- Limits by logged-in user or IP — API keys need Sanctum tokens or your own table
- Counts sit in your cache store, so the cache has to be shared across servers
- No credit balance: a request can’t cost 5 on one route and 1 on another, or refill each month
- Per-plan limits mean branching inside every limiter closure
- No per-customer usage log to answer “why was I blocked?” or bill against
- API keys issued per customer, prefixed, hashed, and revocable from the dashboard
- A credit balance per customer — price each route, refill every hour, day, week, or month
- Rate limits set per plan, shared by all of a customer’s keys, from 1 second to 24 hours
- The same count on every worker, instance, and region — no Redis to run
- Over the limit? A 429 with Retry-After, and no credits charged
- Every request logged per customer — status, latency, endpoint — in under 5ms
Laravel in three steps, one of them code.
ReqKey runs inside your API, not in front of it. Your server asks one question per request and gets an answer in under 5ms.
- 1
Install the SDK
composer require reqkey/reqkey - 2
Set your project key
Copy it from the dashboard into
REQKEY_PROJECT_KEY. It stays on your server. - 3
Add the Laravel middleware
Every request is checked, charged, and logged before your handler runs.
// config/reqkey.phpreturn [ 'project_key' => env('REQKEY_PROJECT_KEY'), 'api_id' => env('REQKEY_API_ID'), 'middleware' => [ 'key_name' => 'X-Startup-Key', 'exclude_paths' => ['/health'], ],]; // routes/web.php — apply the auto-discovered "reqkey" aliasRoute::middleware('reqkey')->post('/payments', function (Request $request) { $decision = $request->attributes->get('reqkey'); return response()->json([ 'created' => true, 'credits_remaining' => $decision->creditsRemaining, ], 201);});Every request gets one of these answers
- 200Valid key, credits charged — your handler runs
- 402Out of credits
- 403Key disabled, or not allowed on this API
- 429Over the rate limit — no credits charged
Where ReqKey sits
Responses go straight back to your customer. ReqKey sees the key check and the log line, nothing else.
Credits
Charge each route what it costs you.
A lookup can cost 1 credit and a render 5, from the same balance. Excluded paths are never validated, charged, or recorded. In PHP:
new MiddlewareConfig( apiId: 'api_payments', // Exact paths or trailing-* prefixes: never validated, charged, or recorded excludePaths: ['/health', '/docs/*'], // Or decide per request with a predicate shouldProtect: static fn (RequestData $request): bool => str_starts_with($request->path, '/api/'), // Charge endpoints differently — a closure instead of an int credits: static fn (RequestData $request): int => $request->path === '/images' ? 5 : 1,);Rate limits
Set limits on plans, not in code.
Your Laravel code never hard-codes a number. Each plan carries its credits, refill, and rate limit; moving a customer to Pro changes all three with no deploy.
Example plans. You name them and pick the numbers.
Notes for Laravel teams hit in production.
Auto-discovered
The service provider registers itself and the reqkey middleware alias. Publish config/reqkey.php and you’re done.
Sanctum is for your app, ReqKey for your API
Keep Sanctum or Breeze for dashboard logins; issue ReqKey keys to the customers who call your paid API.
Octane-friendly
Counts and balances live outside the PHP process, so long-running workers under Octane stay correct.
Laravel rate limiting: the questions teams ask.
Something else? Ask the team or read the docs.
On routes protected by ReqKey, yes — ReqKey applies each customer’s plan limit. Keep throttle on public or session routes.
Run php artisan vendor:publish --tag=reqkey-config, then set REQKEY_PROJECT_KEY and REQKEY_API_ID in .env.
On their plan or on the customer (consumer) in ReqKey — a number of requests per window from 1 second to 24 hours, shared by all of that customer’s keys. Your Laravel code never hard-codes a limit, so upgrading a customer is a dashboard change, not a deploy.
A check averages under 5ms. ReqKey runs inside your app as middleware, not as a gateway in front of it, so responses go straight back to your customer.
You choose: fail closed and answer 503, or fail open and let requests through. Invalid keys are denied either way, and a validation is never retried, so no one is charged twice.
Ship API keys in Laravelin five minutes.
Free for your first 5 million requests every month. No card, no gateway, no rewrite.