Cuotas y errores
Cada key tiene una cuota diaria que depende de su tier y se reinicia a medianoche en America/Santiago. Además, cada IP tiene un límite de 10 requests por segundo (ráfagas de hasta 20), independiente de la key y del tier.
Límites por tier
| Tier | Cuota diaria por defecto | Cómo se obtiene |
|---|---|---|
free | 300 requests/día | Registro en api.buscafondos.com/register, sin costo. |
institutional | Se acuerda según el caso de uso. Referencia: 25.000 requests/día | Solicitud a api@buscafondos.com con requerimiento y justificación. |
internal | Sin límite diario (por defecto) | Reservado para servicios propios de BuscaFondos. No se asigna a terceros. |
- Desde el 1 de octubre de 2026, la cuota del tier
freebajó de 5.000 a 300 requests/día. Las keysfreeexistentes siguen funcionando sin cambios en autenticación. - El límite de 10 requests/segundo por IP (ráfagas de 20) aplica a todos los tiers, incluido
institutional. Repartir tráfico entre muchas IPs para evadirlo se considera abuso. - Una key puede tener una cuota asignada distinta a la del tier. La cuota vigente de tu key es la que informa el header
X-RateLimit-Limiten las respuestas de datos, odaily_quotaenGET /api/key/info. - Cada request autenticada descuenta cuota, aunque responda
4xxo5xx. SoloGET /api/key/infoy los429por cuota agotada no descuentan. - Al agotar la cuota, la API responde
429conX-RateLimit-Remaining: 0hasta el próximo reinicio diario (X-RateLimit-Reset). No reintentes antes de esa hora. Un429por exceso de tasa por IP, en cambio, traeRetry-After: 1y no incluye headersX-RateLimit-*.
Cómo rendir la cuota
GET /api/all-fundsentrega en una sola request el listado completo de fondos vigentes con precio, variaciones diaria y mensual, y TAC. Úsalo en lugar de recorrer serie por serie.- Guarda en caché las respuestas: los valores cuota se publican una vez al día.
GET /api/key/infomuestra tu tier, cuota y consumo del día, y no descuenta cuota.- Si necesitas el dataset completo o actualizaciones periódicas de muchos fondos, escribe antes de automatizar la descarga. Puede existir una forma más eficiente para ti y para la API.
Headers de cuota
| Header | Significado |
|---|---|
X-RateLimit-Limit | Cuota diaria asignada a la key. |
X-RateLimit-Remaining | Requests disponibles después de la llamada. |
X-RateLimit-Reset | Timestamp Unix del próximo reinicio. |
Retry-After | Segundos a esperar antes de reintentar. Aparece en 429 y en algunos 503. |
Los cuatro headers están expuestos vía CORS, así que también son legibles desde el navegador.
Códigos de estado del middleware
401 Unauthorized: falta la key, es inválida o fue revocada. IncluyeWWW-Authenticate: Bearer.403 Forbidden: no forma parte del flujo de API key en los endpoints de datos. Hoy solo aparece en las rutas de cuenta cuando el origen no es confiable. No lo interpretes como cuota agotada.429 Too Many Requests: cuota diaria agotada o exceso de tasa por IP. Cuando se agota la cuota, incluye los cuatro headers anteriores,X-RateLimit-Remainingen0y unRetry-Afterque puede llegar a horas. El exceso de tasa respondeRetry-After: 1sin headersX-RateLimit-*.5xx: falla transitoria o datos en actualización. La validación de keys y algunos resúmenes pueden responder503conRetry-After: 5.
Retry con backoff y jitter
async function requestWithRetry(url: string, apiKey: string) {
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch(url, {
headers: { "X-Api-Key": apiKey },
});
if (response.ok || ![429, 500, 502, 503, 504].includes(response.status)) {
return response;
}
// Cuota diaria agotada: no tiene sentido reintentar hasta el reinicio.
if (response.status === 429 && response.headers.get("X-RateLimit-Remaining") === "0") {
return response;
}
const retryAfterHeader = response.headers.get("Retry-After");
const retryAfterSeconds =
retryAfterHeader === null || retryAfterHeader.trim() === ""
? Number.NaN
: Number(retryAfterHeader);
const fallbackMs = 500 * 2 ** attempt + Math.random() * 250;
const waitMs = Number.isFinite(retryAfterSeconds) && retryAfterSeconds >= 0
? retryAfterSeconds * 1000
: fallbackMs;
await new Promise((resolve) => setTimeout(resolve, waitMs));
}
throw new Error("BuscaFondos no respondió después de 4 intentos");
}No reintentes 401 ni 403: corrige la autenticación primero. Antes de automatizar cargas grandes, lee la política de uso.