# 🚦 Rate Limiting + Quotas
## 🤔 ¿Qué hago? ¿Cómo lo hago? ¿Y para qué lo hago?
**¿Qué hago:**
Klaus-proxy implementa un sistema de rate limiting por API key con quotas configurables en tres ventanas: por minuto, por hora y por día. Cuando un tenant excede su cuota, la plataforma retorna un HTTP **429 Too Many Requests** con detalles de uso actual.
**¿Cómo lo hago:**
- **RateLimitStore**: Abstracción base para almacenar contadores (implementación: MemoryRateLimitStore)
- **QuotaManager**: Orquesta las verificaciones de cuota y registro de uso
- **Middleware en /v1/messages**: Intercepta requests antes del procesamiento, verifica cuota, rechaza si se excedió
- **Endpoints de introspección**: `/quotas/usage` (ver uso actual) y `/quotas/reset` (resetear manual)
**¿Para qué lo hago:**
- Prevenir abuso de recursos en entornos multi-tenant
- Garantizar fair-share entre clientes
- Proteger backends upstream (Anthropic, Qdrant) de bombardeos
- Proporcionar observabilidad de consumo por tenant
---
## 🏗️ Arquitectura
```mermaid
graph TD
A["🔐 Client Request<br/>Header: x-api-key"] --> B["FastAPI Middleware"]
B --> C["QuotaManager.check_quota"]
C --> D{"Quota<br/>Exceeded?"}
D -->|Yes| E["429 Too Many Requests"]
D -->|No| F["Process Request"]
F --> G["QuotaManager.record_usage"]
G --> H["Return Response"]
E --> I["X-RateLimit-Reset header"]
H --> J["X-Cache + X-Provider headers"]
K["RateLimitStore<br/>per_minute, per_hour, per_day"] --> C
K --> G
L["/quotas/usage<br/>GET"] --> M["Return current usage"]
N["/quotas/reset<br/>POST"] --> O["Manual reset"]
```
---
## 📊 Límites por defecto
```python
DEFAULT_LIMITS = {
"per_minute": 60, # 60 requests/minuto
"per_hour": 1000, # 1000 requests/hora
"per_day": 10000, # 10000 requests/día
}
```
**Configuración personalizada:** Crear `QuotaManager(limits={...})` en main.py.
---
## 🔌 API Endpoints
### GET /quotas/usage
Obtiene el uso actual del tenant.
**Request:**
```bash
curl -H "x-api-key: my-api-key" http://localhost:8000/quotas/usage
```
**Response:**
```json
{
"api_key": "my-api-key",
"usage": {
"per_minute": 45,
"per_hour": 892,
"per_day": 5234
},
"limits": {
"per_minute": 60,
"per_hour": 1000,
"per_day": 10000
},
"timestamp": "2026-09-10T10:30:45.123456+00:00"
}
```
### POST /quotas/reset
Resetea la cuota de un tenant.
**Request:**
```bash
# Resetear solo per_minute
curl -X POST \
-H "x-api-key: my-api-key" \
-H "Content-Type: application/json" \
-d '{"window": "per_minute"}' \
http://localhost:8000/quotas/reset
# Resetear todas las ventanas
curl -X POST \
-H "x-api-key: my-api-key" \
-H "Content-Type: application/json" \
-d '{}' \
http://localhost:8000/quotas/reset
```
**Response:**
```json
{
"status": "reset",
"api_key": "my-api-key",
"window": "per_minute"
}
```
---
## ⚙️ Integración en endpoints protegidos
El rate limiting se aplica **automáticamente** en estos endpoints:
### Endpoints Message
- `/v1/messages` — Llamadas a Anthropic API
### Endpoints Deferred (alto consumo de recursos)
- `/knowledge/ingest/file` — Ingest de archivos locales
- `/sources/ingest/web` — Web crawling con Crawl4AI
- `/sources/ingest/pdf` — Extracción de PDF con OCR
**Flujo en cada endpoint:**
1. **Extrae x-api-key** del header (default: "default-tenant")
2. **Verifica cuota** vía `QuotaManager.check_quota(api_key)`
3. **Si excedida:** Retorna 429 + X-RateLimit-Reset header
4. **Si válida:** Continúa procesamiento normal
5. **Al finalizar:** Registra uso vía `QuotaManager.record_usage(api_key)`
---
## 💻 Ejemplo de integración
```python
from proxy.rate_limit import get_quota_manager, MemoryRateLimitStore
# En main.py lifespan (opcional — se inicializa lazy)
# quota_mgr = get_quota_manager()
# En endpoint /v1/messages
api_key = incoming_headers.get("x-api-key", "default-tenant")
quota_mgr = get_quota_manager()
# Check antes de procesar
quota_check = await quota_mgr.check_quota(api_key)
if not quota_check["allowed"]:
return JSONResponse(
status_code=429,
content={"error": quota_check["reason"], "usage": quota_check["usage"]},
headers={"X-RateLimit-Reset": str(reset_at)},
)
# ... procesar request ...
# Record al finalizar
await quota_mgr.record_usage(api_key)
```
---
## 🧪 Tests
**24 tests** cobriendo:
- ✅ Incrementos de contador
- ✅ Verificación de límites (per_minute, per_hour, per_day)
- ✅ Aislamiento entre API keys
- ✅ Aislamiento entre ventanas
- ✅ Reset específico y total
- ✅ Acceso concurrente
- ✅ Límites personalizados
- ✅ Edge cases (claves vacías, reset no-existen, etc.)
**Ejecutar tests:**
```bash
pytest tests/test_rate_limit.py -v
```
---
## 🔮 Extensiones futuras
1. **Redis backend:** Implementar `RedisRateLimitStore` para multi-proceso
2. **Quotas por recurso:** Diferenciar límites por endpoint (e.g., /v1/messages vs /knowledge/ingest)
3. **Tiered pricing:** Límites variables según plan de suscripción
4. **Metrics export:** Exponer contadores a Prometheus para alertas
5. **Admin panel:** Dashboard para revisar/resetear quotas
---
## 🔐 Notas de seguridad
- **x-api-key no es autenticación:** Solo identificador de tenant. Usar autenticación fuerte en /knowledge/ingest.
- **Default tenant fallback:** Si no se envía x-api-key, se usa "default-tenant" compartido — configurar si es multi-tenant.
- **Clock skew:** Las ventanas de tiempo se resetan basándose en `time.time()` — reloj del servidor debe estar sincronizado.
- **Memory store para desarrollo:** MemoryRateLimitStore es suficiente para single-process. Para producción multi-proceso, usar Redis.