
September 12, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
Every public API eventually faces the same problem: one client sends too many requests and everyone else slows down. Rate Limiting and Throttling API Design is how you stop that before it becomes an outage, a surprise cloud bill, or a partner integration that hammers your database at 3 a.m. On production Laravel applications I maintain, rate limits sit beside authentication and input validation—not as an optional hardening step. This guide walks through the algorithms, HTTP contract, Laravel 13 patterns, and gateway options you can ship this week. For deeper Laravel-specific wiring, see the companion piece on rate limiting and API throttling in Laravel.
What Is the Difference Between Rate Limiting and Throttling in API Design?
People use the terms interchangeably, but they solve slightly different problems. Rate limiting rejects excess traffic once a quota is exhausted. Throttling slows traffic down—queuing, delaying, or shedding load—so some requests still get through instead of failing immediately.
In practice, most REST APIs implement rate limiting with a hard ceiling. Throttling appears more often at the edge (API gateways, CDN WAFs) or inside job queues where delaying a webhook retry is acceptable. Your design should state which behaviour clients will see.
A solid REST API design baseline treats limits as part of the contract. Partners plan retries around your headers. Internal teams avoid surprise denials during batch jobs. That contract belongs in your OpenAPI spec and your SDK error handling.
| Approach | Behaviour on exceed | Best for | Client experience |
|---|---|---|---|
| Hard rate limit | HTTP 429 immediately | Public REST APIs, auth endpoints | Predictable; requires retry logic |
| Soft throttle (queue) | Request waits in queue | Internal microservices, webhooks | Higher latency, fewer hard failures |
| Adaptive throttle | Limit drops under load | Shared SaaS during traffic spikes | Protects platform; harder to document |
| Cost-based limit | Heavy endpoints cost more tokens | Search, AI, report generation | Fair usage by resource weight |
Which Rate Limiting Algorithms Should You Use for APIs?
Four algorithms cover nearly every production API. Pick based on burst tolerance, memory cost, and how precisely you need the window boundary to behave.
Fixed window counter
Count requests per calendar minute or hour. Implementation is trivial: one Redis key per client per window. The flaw is the "double burst" at window edges—a client can send 100 requests at 00:59 and 100 more at 01:00.
Sliding window log
Store timestamps of each request and drop entries older than the window. Accurate but memory-heavy at high volume. Good for low-traffic admin APIs or per-IP login protection.
Sliding window counter
Blend the current and previous fixed windows with a weighted count. You get near sliding-window accuracy with O(1) Redis operations. This is what many gateways use internally.
Token bucket
Tokens refill at a steady rate. Each request consumes one token. Clients can burst up to bucket capacity, then settle to the refill rate. Ideal for partner APIs where occasional spikes are fine but sustained abuse is not.
For a deeper algorithm walkthrough, read token bucket and sliding window rate limiting. On a legal-tech portal I built, login endpoints used a tight sliding window while document download APIs used token buckets with higher burst capacity.
How Do You Return HTTP 429 and Rate Limit Headers Correctly?
When a client exceeds its quota, return 429 Too Many Requests. The status code is defined in RFC 6585. Include headers that tell integrators exactly what happened and when they can retry.
Standard response headers:
- Retry-After — seconds (integer) or HTTP-date until the client should retry
- X-RateLimit-Limit — maximum requests allowed in the window
- X-RateLimit-Remaining — requests left in the current window
- X-RateLimit-Reset — Unix timestamp when the window resets
Some teams adopt the draft RateLimit header fields (`RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`). Pick one convention and use it consistently across all endpoints.
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 42
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1726123456
{
"type": "https://api.example.com/errors/rate-limit",
"title": "Rate limit exceeded",
"status": 429,
"detail": "You exceeded 120 requests per minute.",
"retry_after": 42
} Use JSON formatter tools to validate error payloads during development. Problem Details (RFC 9457) give SDK authors a stable parsing surface. Never return 503 for rate limiting—that tells clients your server is down, not that they should back off.
How Do You Implement Rate Limiting in Laravel 13?
Laravel ships built-in rate limiting via RateLimiter and route middleware. On PHP 8.3+ with Laravel 13, configuration lives in bootstrap/app.php or a dedicated service provider. Back the limiter with Redis 8.10 in production so limits survive horizontal scaling.
Define named limiters
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(120)
->by($request->user()?->id ?: $request->ip())
->response(function (Request $request, array $headers) {
return response()->json([
'title' => 'Rate limit exceeded',
'status' => 429,
], 429, $headers);
});
});
RateLimiter::for('exports', function (Request $request) {
return [
Limit::perMinute(5)->by($request->user()->id),
Limit::perDay(50)->by($request->user()->id),
];
}); Attach middleware to routes
Route::middleware(['auth:sanctum', 'throttle:api'])
->prefix('v1')
->group(function () {
Route::get('/orders', [OrderController::class, 'index']);
Route::post('/exports', [ExportController::class, 'store'])
->middleware('throttle:exports');
}); Key the limiter by something stable: authenticated user ID, API key ID, or OAuth client ID. IP-only keys break behind carrier-grade NAT and punish mobile users in Nepal and elsewhere. For custom key strategies, see Laravel rate limiting with custom keys.
Set CACHE_STORE=redis in production. File or database cache backends do not share state across app servers. I've debugged "limits that only work on one node" more than once—that is almost always a cache driver issue.
- Define limiters in a service provider boot method.
- Apply
throttlemiddleware on route groups. - Return structured 429 JSON with standard headers.
- Store counters in Redis, not local memory.
- Log rate-limit hits with client ID for abuse review.
- Document every limiter name in OpenAPI.
More patterns appear in Laravel API best practices and the guide on building RESTful APIs with Laravel.
Where Should Rate Limits Live: Gateway, App, or Both?
Defence in depth wins. An API gateway (Kong, Traefik, AWS API Gateway) drops obvious abuse before PHP-FPM ever spins up. Application-level limits enforce business rules the gateway cannot know—per-tenant quotas, expensive endpoint weights, or role-based tiers.
On shared EC2 infrastructure where I run Deployer 7 releases, edge limits protect the whole fleet during traffic spikes. App limits catch authenticated abuse that shares one NAT IP. Read Kong as an API gateway and Traefik as an API gateway for edge deployment options.
Choosing limit keys and tiers
Free-tier SaaS APIs often allow 60 requests per minute. Paid tiers get 600 or 6,000. Store tier metadata on the user or API key record. Resolve the limit in middleware before the controller runs.
Cost-weighted limits matter when some endpoints are expensive. A product search might cost 1 token. A PDF report export might cost 20. This pattern prevents one integration from monopolising workers during month-end reporting.
Webhooks and async work
Outbound webhooks need their own outbound rate limits. Payment gateways and SMS providers will ban your IP if you retry aggressively. Queue webhook delivery with backoff and cap concurrent sends per destination. See webhook design and security and webhook design patterns for reliability.
How Do You Design Rate Limits Partners Can Actually Integrate With?
Limits nobody knows about become support tickets. Document them in OpenAPI, publish a status page note when limits change, and ship an SDK that parses 429 responses automatically.
OpenAPI documentation
Describe global limits in the API info block. Annotate expensive routes individually. Tools like Scribe for Laravel generate docs from your actual middleware config—see design a REST API with OpenAPI and Swagger.
SDK retry logic
Your public SDK should read Retry-After, sleep, and retry idempotent requests. For POST requests, require idempotency keys so a retried payment does not double-charge. Full SDK guidance lives in SDK design for your public API.
Monitoring and alerting
Track rate-limit hit rate per client, per endpoint, and globally. A sudden spike on one API key may mean a runaway cron job—not an attack. Wire metrics into Prometheus and Grafana as described in API monitoring with Prometheus and Grafana.
On the Quick And Easy Nepalese Grocery Laravel eCommerce platform, checkout and inventory APIs used separate limiters. Checkout stayed tight to prevent card-testing abuse. Catalog reads got a higher burst because mobile clients prefetch product lists.
Security overlap
Rate limiting is not a substitute for authentication. It complements it. Login and password-reset endpoints deserve the strictest limits—often per IP and per username combined. Brute-force protection overlaps with rate limiting to stop brute-force attacks and the broader API security checklist.
Abuse prevention goes further: block known bad ASNs, require API keys for all non-public routes, and rotate leaked keys quickly. The practical abuse guide at API rate limiting and abuse prevention covers patterns I use on production systems.
Versioning and deprecation
When you ship API v2, do not silently tighten v1 limits to force migration—that breaks trust. Announce limit changes in changelog emails and sunset headers. API versioning strategies and deprecation best practices pair naturally with quota policy.
Redis implementation sketch
For custom limiters outside Laravel's facade, atomic INCR with TTL in Redis avoids race conditions. The Redis INCR command documentation shows the primitive most libraries wrap.
/*
Pseudocode: sliding window with Redis INCR + EXPIRE
Key: ratelimit:{client_id}:{window_start_minute}
*/
count = redis.incr(key)
if count == 1:
redis.expire(key, 60)
if count > limit:
return 429 with Retry-After = ttl(key) For multi-region deployments, prefer a central Redis cluster or accept eventually consistent limits per region. Strong global consistency is rarely worth the latency cost for rate limiting.
Testing limits before launch
Load-test with realistic client concurrency. Verify 429 headers, confirm Redis keys expire, and ensure a limit breach does not leak stack traces. Include rate-limit scenarios in your CI smoke suite where feasible. Our testing and optimization service often catches missing Retry-After headers during pre-launch audits.
If you are building a new public API from scratch, pairing limit design with API development early saves painful retrofitting. Limits touch routing, caching, billing tiers, and support playbooks.
Key Takeaways
- Rate limiting rejects excess requests with HTTP 429; throttling slows or queues traffic—document which behaviour your API uses.
- Use token bucket or sliding window counters backed by Redis for accurate, scalable limits across multiple app servers.
- Return Retry-After plus X-RateLimit-* headers on every 429 so SDKs and partners retry correctly.
- Enforce limits at both the API gateway and application layer, keyed by user or API key—not IP alone.
- Document quotas in OpenAPI, monitor hit rates per client, and require idempotency keys on retried write operations.
- Weight expensive endpoints higher so one report export cannot consume the same budget as a thousand lightweight reads.
People Also Ask
What HTTP status code should APIs return when rate limited?
Return 429 Too Many Requests with a Retry-After header. Do not use 403 (that implies authorization failure) or 503 (that implies server overload). A JSON error body with a stable error type URL helps SDK parsing.
What is a reasonable API rate limit for free and paid tiers?
Free tiers commonly start at 60–100 requests per minute. Paid tiers scale to 1,000+ depending on infrastructure cost. Match limits to actual server capacity and endpoint cost. Publish exact numbers rather than vague "fair use" policies.
Should rate limiting use IP address or API key?
Prefer API key or authenticated user ID for identified clients. Use IP as a fallback for anonymous endpoints like login and password reset. Combine both on auth routes to block distributed credential stuffing.
How does rate limiting differ from DDoS protection?
Rate limiting enforces per-client quotas under normal traffic shapes. DDoS protection handles volumetric attacks at the network or CDN edge. You need both, but they operate at different layers and thresholds.
Ship Rate Limits Before You Need Them
Rate Limiting and Throttling API Design is cheapest to get right at launch and expensive to bolt on after an partner integration or scraper takes your API offline. Pick an algorithm, wire Redis, return proper 429 headers, document the contract, and test under load. Your future on-call self will thank you.
Need help designing or auditing limits on a Laravel, Symfony, or custom PHP API? Contact us to review your current setup, or explore related reading on rate limiting strategies for APIs and REST API design best practices in 2026. You can also browse the Mijar Law Associates client portal work and other portfolio projects where production API limits protect real business workflows.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

