Rate Limiting¶
PrimeStamp uses configurable per-client rate limiting to protect the API. Two algorithms are available: token bucket (default, burst-friendly) and sliding window (smooth limiting).
Configuration¶
Rate limiting is added as FastAPI middleware:
from primestamp.api.rate_limit import add_rate_limiting, RateLimitAlgorithm
app = FastAPI()
# Default: token bucket, anonymous tier
add_rate_limiting(app)
# Or customize:
add_rate_limiting(
app,
algorithm=RateLimitAlgorithm.SLIDING_WINDOW,
enabled=True,
default_tier="anonymous",
)
Full configuration via RateLimitConfig:
from primestamp.api.rate_limit import RateLimitConfig, RateLimitMiddleware
config = RateLimitConfig(
algorithm=RateLimitAlgorithm.TOKEN_BUCKET,
default_tier="anonymous",
enabled=True,
cleanup_interval_seconds=300,
max_tracked_clients=10000,
include_headers=True,
exempt_paths={"/health", "/docs", "/redoc", "/openapi.json"},
)
app.add_middleware(RateLimitMiddleware, config=config)
Tiers¶
Each client is assigned a tier based on authentication. Limits apply per minute and per hour.
| Tier | Requests/min | Requests/hour | Burst Size | Identification |
|---|---|---|---|---|
anonymous |
20 | 200 | 5 | Client IP (no API key) |
authenticated |
60 | 1,000 | 15 | Bearer ps_live_... or X-API-Key: ps_... |
premium |
200 | 5,000 | 50 | Premium API key |
internal |
1,000 | 50,000 | 200 | Internal services / test clients |
Client Identification Priority¶
Authorization: Bearer ps_live_...orBearer ps_test_...header --authenticatedtierX-API-Key: ps_...header --authenticatedtierX-Forwarded-FororX-Real-IPheader -- IP-based,anonymoustier- Direct client IP --
anonymoustier
Test clients (IP testclient) are automatically assigned the internal tier.
Endpoint Weights¶
Not all endpoints cost the same. Expensive operations consume more of the rate limit budget.
| Endpoint Prefix | Cost Multiplier | Rationale |
|---|---|---|
/stamps |
2.0x | Stamp creation involves hashing and anchoring |
/fingerprints |
1.5x | Fingerprinting is compute-intensive |
/verify |
1.0x | Standard cost |
/identities |
1.0x | Standard cost |
/health |
0.1x | Health checks are lightweight |
/presets |
0.1x | Static configuration lookups |
A request to POST /stamps from an anonymous client (burst size 5) costs 2.0 tokens, so only 2 rapid stamp creations are possible before throttling.
Response Headers¶
Every response includes rate limit headers (unless disabled):
| Header | Description |
|---|---|
X-RateLimit-Limit |
Maximum requests per minute for this tier |
X-RateLimit-Remaining |
Remaining requests in the current window |
X-RateLimit-Reset |
Seconds until the rate limit resets |
X-RateLimit-Tier |
Client's assigned tier name |
Example response headers:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 48
X-RateLimit-Reset: 60
X-RateLimit-Tier: authenticated
429 Response¶
When the rate limit is exceeded, the API returns HTTP 429:
{
"error": "rate_limit_exceeded",
"message": "Too many requests. Please slow down.",
"retry_after": 3.2,
"tier": "anonymous",
"limit": 20
}
Additional headers on 429 responses:
| Header | Description |
|---|---|
Retry-After |
Seconds to wait before retrying (integer, rounded up) |
X-RateLimit-Limit |
Tier limit |
X-RateLimit-Remaining |
0 |
X-RateLimit-Reset |
Same as Retry-After |
Algorithms¶
Token Bucket (default)¶
The token bucket algorithm allows short bursts up to burst_size while enforcing a sustained rate. Tokens refill at requests_per_minute / 60 tokens per second.
- Allows bursty traffic patterns
- Capacity equals the tier's
burst_size - Separate buckets for per-minute and per-hour limits
Sliding Window¶
The sliding window algorithm counts requests within a rolling time window. Provides smoother rate limiting without burst allowance.
- Window sizes: 60 seconds (minute) and 3600 seconds (hour)
- Requests outside the window are discarded
- No burst tolerance
Per-Route Rate Limiting¶
As an alternative to middleware, use the dependency-based approach for per-route control:
from fastapi import Depends
from primestamp.api.rate_limit import rate_limit_dependency
@app.post("/stamps", dependencies=[Depends(rate_limit_dependency)])
async def create_stamp(...):
...
This raises HTTPException(429) directly if the limit is exceeded.
Exempt Paths¶
The following paths are exempt from rate limiting by default:
/health/docs/redoc/openapi.json
Customize via RateLimitConfig.exempt_paths.
Monitoring¶
Get rate limiter statistics:
from primestamp.api.rate_limit import get_rate_limiter_store
store = get_rate_limiter_store()
stats = store.get_stats()
Stale client entries (inactive for 1 hour) are cleaned up automatically every 300 seconds or when max_tracked_clients is exceeded.