Guías
Rate Limiting
Límites de velocidad por plan, headers de respuesta y estrategias de retry en QAPI.
Rate Limiting
QAPI aplica límites de velocidad por API key para garantizar disponibilidad a todos los tenants.
Límites por plan
| Plan | DTEs/mes | Rate limit | Precio |
|---|---|---|---|
| Starter | 500 | 60 req/min | $59.99 |
| Business | 2,000 | 200 req/min | $199.99 |
| Enterprise | Custom | Configurable | Cotización |
Los usuarios autenticados con JWT tienen un límite de 300 req/min independientemente del plan.
Headers de rate limit
Cada respuesta incluye estos headers:
Prop
Type
Respuesta cuando se supera el límite
Ejemplo HTTP
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
Retry-After: 43
Content-Type: application/json
{
"type": "https://docs.dtevia.com.gt/errors/rate-limited",
"title": "RATE_LIMITED",
"status": 429,
"detail": "Límite de requests excedido. Reintentar en 43s",
"instance": "/v1/invoices"
}Estrategia de retry con backoff exponencial
Implementación
async function fetchWithRetry(
url: string,
options: RequestInit,
maxRetries = 3
): Promise<Response> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const res = await fetch(url, options);
if (res.status !== 429) return res;
if (attempt === maxRetries) throw new Error('Max retries reached');
const retryAfter = parseInt(res.headers.get('retry-after') ?? '60', 10);
console.log(`Rate limited. Reintentando en ${retryAfter}s...`);
await new Promise(r => setTimeout(r, retryAfter * 1000));
}
throw new Error('Unreachable');
}import axiosRetry from 'axios-retry';
axiosRetry(axios, {
retries: 3,
retryCondition: (error) => error.response?.status === 429,
retryDelay: (retryCount, error) => {
const retryAfter = parseInt(
error.response?.headers['retry-after'] ?? '60',
10
);
return retryAfter * 1000;
},
});import time
def fetch_with_retry(url: str, max_retries: int = 3, **kwargs) -> httpx.Response:
for attempt in range(max_retries + 1):
res = httpx.post(url, **kwargs)
if res.status_code != 429:
return res
if attempt == max_retries:
raise Exception("Max retries alcanzados")
retry_after = int(res.headers.get("retry-after", "60"))
print(f"Rate limited. Reintentando en {retry_after}s...")
time.sleep(retry_after)
raise Exception("Unreachable")Errores de límite de cuota
Rate limit vs otros límites del plan
Distintos de rate limiting (velocidad), existen otros límites por plan:
| Error | Causa |
|---|---|
RATE_LIMITED (429) | Demasiadas requests por minuto |
SANDBOX_LIMIT_REACHED (429) | Superaste el límite diario de facturas de prueba por tipo de DTE |
PROJECT_LIMIT_REACHED (429) | Alcanzaste el límite de proyectos activos de tu plan |
MONTHLY_QUOTA_EXCEEDED (429) | El proyecto agotó su cuota mensual de DTEs asignada (solo live) |
PROJECT_LIMIT_REACHED es del dashboard, no de facturación
Este error ocurre al crear proyectos en el dashboard cuando llegaste al máximo de proyectos activos de tu plan (starter: 3, business: 8, enterprise: sin límite). No ocurre al emitir facturas. Para resolverlo: archiva un proyecto que no uses o actualiza tu plan.
Buenas prácticas
- Lee
X-RateLimit-Remainingy ralentiza proactivamente cuando se acerque a 0 - Usa colas para emisión masiva en lugar de enviar en ráfaga
- Implementa backoff que respeta el
Retry-Afterexacto (no adivines el tiempo) - Guarda
X-Request-IDen tu log para cada factura — facilita el soporte - Distribuye la carga a lo largo del minuto si tienes muchas facturas simultáneas