Rate Limiting¶
Craft Easy API uses Slowapi (built on top of limits) to enforce token-bucket rate limiting on all endpoints.
Configuration¶
| Setting | Default | Description |
|---|---|---|
RATE_LIMIT_ENABLED |
true |
Enable/disable rate limiting globally |
RATE_LIMIT_DEFAULT |
"100/minute" |
Default limit for all authenticated endpoints |
RATE_LIMIT_AUTH |
"10/minute" |
Stricter limit for authentication endpoints |
RATE_LIMIT_STORAGE_URI |
"memory://" |
Storage backend URI — set automatically when REDIS_URI is configured |
REDIS_URI |
None |
Redis connection URI (e.g. "redis://localhost:6379/0"). When set, automatically overrides RATE_LIMIT_STORAGE_URI to use Redis. |
PUBLIC_USER_RATE_LIMIT |
"60/minute" |
Rate limit for anonymous/public users (applied when PUBLIC_USER_ENABLED=true) |
Rate Limit Key¶
The limiter identifies callers using a smart key function:
- Authenticated users: keyed by
user:{user_id}— ensures per-user limits regardless of IP - Anonymous requests: keyed by the client's IP address
This prevents one user behind a shared IP (e.g., corporate NAT) from exhausting the limit for everyone.
Storage Backends¶
| Backend | URI format | Use case |
|---|---|---|
| In-memory | memory:// |
Development, single-instance deployments |
| Redis | redis://host:6379/0 |
Production, multi-instance deployments |
Redis Backend¶
For production environments running multiple API instances behind a load balancer, configure Redis so every instance shares the same rate limit counters:
When REDIS_URI is set at startup, the settings validator automatically sets RATE_LIMIT_STORAGE_URI to the same value — you do not need to configure both.
Redis support requires the redis extra:
In-memory storage is the default and is appropriate for local development and single-instance deployments. If REDIS_URI is not set, the rate limiter uses in-memory storage automatically.
Fallback to in-memory¶
If REDIS_URI is configured but the redis package is not installed, the API logs a warning at startup and falls back to in-memory storage:
WARNING: REDIS_URI is set but the 'redis' package is not installed.
Install with: pip install 'craft-easy-api[redis]'.
Falling back to in-memory rate limiting.
The API continues to start and serve requests — rate limiting works but is not shared across instances.
Multi-instance without Redis
Running multiple API instances without Redis means each instance keeps its own counters. A client could exceed the intended limit by spreading requests across instances. Use Redis in any horizontally-scaled deployment.
Public (Anonymous) Rate Limiting¶
When PUBLIC_USER_ENABLED=true, anonymous requests are tracked in a separate bucket keyed by public:{ip} and subject to PUBLIC_USER_RATE_LIMIT (default: 60/minute). This stricter limit prevents unauthenticated clients from consuming capacity intended for authenticated users.
Response on Limit Exceeded¶
When a client exceeds the rate limit, the API returns:
HTTP/1.1 429 Too Many Requests
Retry-After: 42
X-Request-ID: abc-123
{
"error": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded",
"retry_after": 42
}
The Retry-After header indicates when the client can retry (in seconds).
Per-Route Limits¶
You can apply custom limits to specific routes using Slowapi decorators:
from slowapi import Limiter
@app.get("/expensive-report")
@limiter.limit("5/minute")
async def expensive_report():
...
Auth Rate Limiting¶
Authentication endpoints (login, OTP, verify) have a stricter rate limit configured via RATE_LIMIT_AUTH. This protects against brute-force attacks:
Additional auth-specific protections are covered in Abuse Protection.