Skip to content
Back to skills

Apply Api Rate Limiting

ASecurity

Use when building any API endpoint — especially authentication endpoints, resource-creation endpoints, and any operation with a per-user or per-tenant cost — to protect against abuse, brute force, and resource exhaustion.

  • 4 stars
  • 0 votes
  • 0 copies
  • 6 views
  • Added September 8, 2026
ai-agentspythonrustgonodeflaskawsazuregitapibackend

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add jeffreytse/grimoire-core --skill apply-api-rate-limiting --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Apply Api Rate Limiting?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Apply Api Rate Limiting
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/jeffreytse-apply-api-rate-limiting/badge)](https://www.skillsdirectory.com/skills/jeffreytse-apply-api-rate-limiting)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: apply-api-rate-limiting
description: Use when building any API endpoint — especially authentication endpoints, resource-creation endpoints, and any operation with a per-user or per-tenant cost — to protect against abuse, brute force, and resource exhaustion.
source: 'OWASP API Security Top 10 2023 API4 (Unrestricted Resource Consumption); OWASP API Security Cheat Sheet (owasp.org/www-project-cheat-sheets); CWE-770'
tags: [security, owasp, rate-limiting, throttling, api, dos-prevention, developer]
---

# Apply API Rate Limiting

Enforce per-client request quotas using token bucket or sliding window algorithms — protecting against brute force, scraping, and resource exhaustion attacks.

## Why This Is Best Practice

**Adopted by:** OWASP API Security Top 10 2023 API4 (Unrestricted Resource Consumption) is a top API vulnerability. AWS API Gateway, Google Cloud Endpoints, Azure APIM, Kong, Nginx, and Cloudflare all provide built-in rate limiting. Stripe, GitHub, and Twitter/X enforce rate limits on every API endpoint with well-documented headers. PCI DSS v4.0 Requirement 8.3.4 mandates account lockout after failed authentication attempts — which requires rate limiting.
**Impact:** Rate limiting prevents brute force credential attacks (see also `design-session-management`), credential stuffing (using credential lists), API scraping (competitive intelligence theft), and denial of service via resource exhaustion. Without authentication endpoint rate limits, attackers test millions of passwords in hours. Without resource endpoint limits, a single user can consume 100% of server capacity.
**Why best:** Client-side debouncing and application-level retries are the alternatives — they don't prevent malicious clients from ignoring them. Server-side rate limiting enforced at the network edge (or application layer with a distributed counter) is the only enforceable mechanism.

Sources: OWASP API Security Top 10 2023 API4; OWASP API Security Cheat Sheet; CWE-770; Stripe rate limiting design

## Steps

1. **Implement token bucket rate limiting** — smooth limit that allows short bursts:

   ```python
   import time
   import redis

   class TokenBucket:
       def __init__(self, redis_client, key, rate, capacity):
           self.redis = redis_client
           self.key = key
           self.rate = rate          # tokens added per second
           self.capacity = capacity  # max tokens (burst size)

       def consume(self, tokens=1):
           now = time.time()
           pipe = self.redis.pipeline()
           pipe.hgetall(self.key)
           results = pipe.execute()
           data = results[0]

           last_time = float(data.get(b'last_time', now))
           stored = float(data.get(b'tokens', self.capacity))

           # Add tokens for elapsed time
           elapsed = now - last_time
           stored = min(self.capacity, stored + elapsed * self.rate)

           if stored < tokens:
               return False  # rate limited

           stored -= tokens
           pipe = self.redis.pipeline()
           pipe.hset(self.key, mapping={'tokens': stored, 'last_time': now})
           pipe.expire(self.key, int(self.capacity / self.rate) + 1)
           pipe.execute()
           return True
   ```

2. **Apply different limits by endpoint sensitivity**:

   ```python
   RATE_LIMITS = {
       'auth_login':        {'rate': 5,    'capacity': 5,   'window': 60},   # 5/min
       'auth_password_reset': {'rate': 3,  'capacity': 3,   'window': 3600}, # 3/hr
       'api_read':          {'rate': 100,  'capacity': 200, 'window': 60},   # 100/min
       'api_write':         {'rate': 30,   'capacity': 60,  'window': 60},   # 30/min
       'api_export':        {'rate': 5,    'capacity': 10,  'window': 3600}, # 5/hr
   }

   def rate_limit_key(endpoint, identifier):
       # identifier = IP for unauthenticated, user_id for authenticated
       return f'ratelimit:{endpoint}:{identifier}'
   ```

3. **Return standard rate limit headers** (RFC 6585 + IETF RateLimit header draft):

   ```python
   from flask import g, request, jsonify

   @app.before_request
   def check_rate_limit():
       endpoint = request.endpoint
       identifier = current_user.id if current_user.is_authenticated else request.remote_addr
       key = rate_limit_key(endpoint, identifier)
       bucket = TokenBucket(redis_client, key, **RATE_LIMITS.get(endpoint, DEFAULT_LIMIT))

       if not bucket.consume():
           response = jsonify({'error': 'Rate limit exceeded. Retry after specified time.'})
           response.headers['Retry-After'] = str(bucket.retry_after_seconds())
           response.headers['X-RateLimit-Limit'] = str(bucket.capacity)
           response.headers['X-RateLimit-Remaining'] = '0'
           response.headers['X-RateLimit-Reset'] = str(int(time.time()) + bucket.retry_after_seconds())
           return response, 429
   ```

4. **Rate limit by multiple dimensions** — IP alone is insufficient (shared NAT, VPNs):

   ```python
   def get_rate_limit_identifiers(request, user):
       identifiers = [f'ip:{request.remote_addr}']
       if user and user.is_authenticated:
           identifiers.append(f'user:{user.id}')
           identifiers.append(f'tenant:{user.tenant_id}')
       return identifiers

   # Check ALL identifiers — fail if any is exceeded
   def is_rate_limited(request, user, endpoint):
       for identifier in get_rate_limit_identifiers(request, user):
           key = rate_limit_key(endpoint, identifier)
           if not TokenBucket(redis_client, key, **limits).consume():
               return True
       return False
   ```

5. **Use middleware/infrastructure-level rate limiting for edge protection**:

   ```nginx
   # Nginx — limit login endpoint to 5 requests/minute per IP
   limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;

   location /api/auth/login {
       limit_req zone=login burst=3 nodelay;
       limit_req_status 429;
       proxy_pass http://backend;
   }
   ```

   Combine with application-level limits for defense-in-depth.

6. **Implement exponential backoff for repeat offenders**:

   ```python
   def get_lockout_duration(violation_count):
       # Exponential: 1s, 2s, 4s, 8s ... up to 1 hour
       return min(2 ** (violation_count - 1), 3600)

   def record_violation(identifier):
       count = redis.incr(f'violations:{identifier}')
       redis.expire(f'violations:{identifier}', 86400)  # reset after 24h
       return count
   ```

## Rules

- Always rate limit authentication endpoints regardless of other protections — credential stuffing is fully automated.
- Rate limit by user ID (not just IP) for authenticated endpoints — a compromised account can still abuse from many IPs.
- Return 429 (Too Many Requests) with `Retry-After` — not 403 (which suggests permanent denial).
- Distributed systems need distributed rate limit counters (Redis, Memcached) — in-memory counters per instance are bypassable by distributing requests across instances.

## Common Mistakes

- **Only rate limiting unauthenticated endpoints** — authenticated users can also abuse resource-intensive endpoints.
- **Using fixed windows** (reset every minute) — vulnerable to bursting at the window boundary; use sliding window or token bucket.
- **Ignoring `X-Forwarded-For` header behind a load balancer** — all clients appear to come from the load balancer's IP; use the rightmost untrusted IP in the header chain.
- **Rate limiting login attempts but not password reset** — password reset is a brute force vector for account enumeration.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…