agentleFS
jars-demojars-demo/jars-skills/skills/rate-limiting/SKILL.md

rate-limiting

Skill0 starsChanged 33 days agoMITView on GitHub

Add rate limiting to protect APIs and services from abuse, brute-force attacks, and traffic spikes. Use when the user asks to add rate limiting, prevent brute-force login attempts, throttle an API endpoint, implement a token bucket or sliding window, add per-user or per-IP limits, or fix a service that is getting hammered by excessive requests.

  • Reads credentials

12 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use secrets-management, observability and 1 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • xfstudioSkill

    secrets-management

    Implement secure secrets management for CI/CD pipelines using Vault, AWS Secrets Manager, or native platform solutions. Use when handling sensitive credentials, rotating secrets, or securing CI/CD environments.

    Also in 3 projects that use this

  • OpenAISkill

    observability

    Vercel Observability expert guidance — Drains (logs, traces, speed insights, web analytics), Web Analytics, Speed Insights, runtime logs, custom events, OpenTelemetry integration, and monitoring dashboards. Use when instrumenting, debugging, or optimizing application performance and user experience on Vercel.

    Also in 3 projects that use this

  • SentrySkill

    security-review

    Finds exploitable application security vulnerabilities in code changes. Use for Warden security scans, appsec review, OWASP-style checks, authentication or authorization bugs, injection, XSS, SSRF, path traversal, secrets, unsafe crypto, webhook verification, open redirects, or sensitive data exposure.

    Also in 3 projects that use this

The file

We split this skill into the rules it gives an agent

6 rules. 0 are common, 5 are distinctive, and 1 is very specific to this project.

  • 0

    Common

    Most of the 10 edited versions we read keep these.

  • 5

    Distinctive

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Read the existing setup first: Check for an existing middleware, reverse proxy config (nginx, Caddy), or API gateway rule before adding …

    line 14
  2. Distinctive:

    Prefer user ID for authenticated endpoints and IP + endpoint for unauthenticated ones.

    line 17
  3. Very specific:

    Return 429 Too Many Requests with a Retry-After header: Never return 200 or 403 for rate limit violations — 429 is the standard and …

    line 18
  4. Distinctive:

    Always send rate limit headers so clients can back off gracefully:

    line 168
  5. Distinctive:

    If you control the reverse proxy, rate limiting there is cheaper — requests are dropped before hitting application code:

    line 184
  6. Distinctive:

    Start conservative and loosen based on real traffic data.

    line 227

“Common” is measured: kept by at least 6 in 10 of the 10 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Rate limiting

Protect endpoints from abuse and overload by enforcing request limits. Rate limiting is both a security control (brute-force, credential stuffing) and a reliability control (runaway clients, traffic spikes). Pick the right algorithm and scope for the threat.

Ground rules

  • Read the existing setup first. Check for an existing middleware, reverse proxy config (nginx, Caddy), or API gateway rule before adding application-level limiting.
  • Scope limits to the right key. IP-based limits are easy to bypass with proxies. Prefer user ID for authenticated endpoints and IP + endpoint for unauthenticated ones.
  • Return 429 Too Many Requests with a Retry-After header. Never return 200 or 403 for rate limit violations — 429 is the standard and clients know how to handle it.
  • Store counters outside the app process. In-memory counters reset on restart and don't work across multiple instances. Use Redis.
  • Never rate-limit health check or readiness endpoints. Load balancers depend on them.

Algorithms

AlgorithmHow it worksBest for
Fixed windowCount requests in a fixed time bucket (e.g. 100 req/min). Resets at the boundary.Simple cases; beware burst at window edge.
Sliding windowCount requests in a rolling window ending now. Smoother than fixed.General API limiting.
Token bucketTokens refill at a steady rate; each request consumes one. Allows short bursts up to bucket size.APIs that should allow occasional bursts.
Leaky bucketRequests queue and are processed at a fixed rate. Excess are dropped.Smoothing output rate; less common in web APIs.

Sliding window or token bucket covers most use cases. Use fixed window only when simplicity matters more than edge-case burst behaviour.


Node.js / Express

Using express-rate-limit + rate-limit-redis

import rateLimit from "express-rate-limit";
import RedisStore from "rate-limit-redis";
import { createClient } from "redis";

const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();

// General API limit — applied globally
export const apiLimiter = rateLimit({
  windowMs: 60 * 1000,       // 1 minute
  max: 100,                  // 100 requests per window per key
  standardHeaders: "draft-7", // sends RateLimit-* headers (RFC 6585 draft)
  legacyHeaders: false,
  store: new RedisStore({
    sendCommand: (...args: string[]) => redis.sendCommand(args),
  }),
  keyGenerator: (req) => {
    // Authenticated: scope to user; unauthenticated: fall back to IP
    return (req.user?.id ?? req.ip) as string;
  },
  handler: (req, res) => {
    res.status(429).json({
      error: { code: "RATE_LIMITED", message: "Too many requests. Please try again later." },
    });
  },
});

// Strict limit for auth endpoints — brute-force protection
export const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minutes
  max: 10,                   // 10 attempts per 15 min per IP
  standardHeaders: "draft-7",
  legacyHeaders: false,
  store: new RedisStore({
    sendCommand: (...args: string[]) => redis.sendCommand(args),
  }),
  keyGenerator: (req) => req.ip as string,
  skipSuccessfulRequests: true,  // only count failures toward the limit
  handler: (req, res) => {
    res.status(429)
      .set("Retry-After", "900")
      .json({
        error: { code: "TOO_MANY_ATTEMPTS", message: "Too many attempts. Try again in 15 minutes." },
      });
  },
});

// Apply
app.use("/api/", apiLimiter);
app.post("/auth/login", authLimiter, loginHandler);
app.post("/auth/forgot-password", authLimiter, forgotPasswordHandler);

Python / FastAPI

Using slowapi (wraps limits library)

from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
from fastapi import Request
from fastapi.responses import JSONResponse

# Key function: use user ID for authenticated routes, IP for public ones
def get_limit_key(request: Request) -> str:
    user = getattr(request.state, "user", None)
    return str(user.id) if user else request.client.host

limiter = Limiter(
    key_func=get_limit_key,
    storage_uri=os.environ["REDIS_URL"],  # Redis backend
)
app.state.limiter = limiter

# Custom 429 handler — consistent error shape
@app.exception_handler(RateLimitExceeded)
async def rate_limit_handler(request: Request, exc: RateLimitExceeded):
    return JSONResponse(
        status_code=429,
        headers={"Retry-After": "60"},
        content={"error": {"code": "RATE_LIMITED", "message": "Too many requests. Please try again later."}},
    )

# Per-route limits
@app.get("/api/search")
@limiter.limit("30/minute")
async def search(request: Request, q: str): ...

@app.post("/auth/login")
@limiter.limit("10/15minutes")
async def login(request: Request, body: LoginRequest): ...

Manual sliding window with Redis (no library)

import time, redis

r = redis.from_url(os.environ["REDIS_URL"])

def check_rate_limit(key: str, limit: int, window_seconds: int) -> bool:
    """Returns True if the request is allowed, False if rate limited."""
    now = time.time()
    window_start = now - window_seconds

    pipe = r.pipeline()
    pipe.zremrangebyscore(key, 0, window_start)       # remove old entries
    pipe.zadd(key, {str(now): now})                   # add current request
    pipe.zcard(key)                                   # count in window
    pipe.expire(key, window_seconds)
    results = pipe.execute()

    count = results[2]
    return count <= limit

Response headers

Always send rate limit headers so clients can back off gracefully:

RateLimit-Limit: 100          # max requests allowed in the window
RateLimit-Remaining: 43       # requests left in the current window
RateLimit-Reset: 1705329660   # unix timestamp when the window resets
Retry-After: 47               # seconds until the client may retry (on 429 only)

express-rate-limit with standardHeaders: "draft-7" sends these automatically. For manual implementations, add them yourself before every response on limited routes.


Nginx / reverse proxy (before the app)

If you control the reverse proxy, rate limiting there is cheaper — requests are dropped before hitting application code:

# nginx.conf
http {
  # Define a shared memory zone keyed on client IP
  limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
  limit_req_zone $binary_remote_addr zone=auth:10m rate=1r/s;

  server {
    location /api/ {
      limit_req zone=api burst=20 nodelay;
      limit_req_status 429;
      proxy_pass http://app;
    }

    location /auth/login {
      limit_req zone=auth burst=5;
      limit_req_status 429;
      proxy_pass http://app;
    }
  }
}

burst allows a short queue beyond the rate before returning 429. nodelay serves the burst immediately rather than spacing it out.


What to rate-limit and at what level

Endpoint typeRecommended limitKey
Public read API100–300 req/minIP
Authenticated API300–600 req/minUser ID
Login / token endpoint5–10 attempts / 15 minIP
Password reset / OTP3–5 attempts / 15 minIP + email
File upload10–20 req/minUser ID
Webhook receiver100–500 req/minSending IP
Health checkNo limit—

Start conservative and loosen based on real traffic data. It's easier to raise a limit than to explain why a brute-force attack succeeded.


Checklist

  • Redis (or equivalent) used as the counter store — not in-process memory
  • Auth endpoints (login, password reset, OTP) have strict limits keyed on IP
  • Authenticated routes use user ID as the limit key, not IP
  • 429 Too Many Requests returned on violation — not 200, 403, or 503
  • Retry-After header included in every 429 response
  • RateLimit-* headers sent on all rate-limited routes so clients can self-throttle
  • Health check and readiness endpoints are excluded from rate limiting
  • Limits tested: confirm a 429 is actually returned after the threshold is hit
  • Multi-instance safe: counters shared via Redis, not per-process

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.