Documentación

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

TierCuota diaria por defectoCómo se obtiene
free300 requests/díaRegistro en api.buscafondos.com/register, sin costo.
institutionalSe acuerda según el caso de uso. Referencia: 25.000 requests/díaSolicitud a api@buscafondos.com con requerimiento y justificación.
internalSin 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 free bajó de 5.000 a 300 requests/día. Las keys free existentes 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-Limit en las respuestas de datos, o daily_quota en GET /api/key/info.
  • Cada request autenticada descuenta cuota, aunque responda 4xx o 5xx. Solo GET /api/key/info y los 429 por cuota agotada no descuentan.
  • Al agotar la cuota, la API responde 429 con X-RateLimit-Remaining: 0 hasta el próximo reinicio diario (X-RateLimit-Reset). No reintentes antes de esa hora. Un 429 por exceso de tasa por IP, en cambio, trae Retry-After: 1 y no incluye headers X-RateLimit-*.

Cómo rendir la cuota

  • GET /api/all-funds entrega 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/info muestra 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

HeaderSignificado
X-RateLimit-LimitCuota diaria asignada a la key.
X-RateLimit-RemainingRequests disponibles después de la llamada.
X-RateLimit-ResetTimestamp Unix del próximo reinicio.
Retry-AfterSegundos 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. Incluye WWW-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-Remaining en 0 y un Retry-After que puede llegar a horas. El exceso de tasa responde Retry-After: 1 sin headers X-RateLimit-*.
  • 5xx: falla transitoria o datos en actualización. La validación de keys y algunos resúmenes pueden responder 503 con Retry-After: 5.

Retry con backoff y jitter

TypeScript
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.