Appearance
Rate limiting
Para mantener la API rápida y barata para todos, aplicamos un límite por API key.
Límites actuales (Fase 1)
| Dimensión | Límite | Ventana |
|---|---|---|
| Por key | 600 requests | 1 minuto |
Los límites se aplican por API key, que identifica a tu integración de forma estable y única.
Si excedes el límite, recibes:
HTTP/1.1 429 Too Many Requests
Retry-After: 35
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0con body:
json
{ "error": "rate_limited", "message": "Too many requests. Please retry later." }Retry-After es el número de segundos que debes esperar antes del próximo intento.
Protección contra brute-force
No imponemos un lockout adicional sobre intentos fallidos: el espacio de claves es de 128 bits aleatorios (32 hex + prefix finova_sk_), así que adivinar un token válido a fuerza bruta es matemáticamente intratable incluso a 600 intentos por minuto. El digest se compara en tiempo constante y el SHA-256 + pepper en la base de datos garantiza que un volcado de la BD no produce claves utilizables.
Estrategia recomendada
- Backoff exponencial ante
429: espera 1s, luego 2s, luego 4s, etc., hasta el límite que decidas (sugerido 30s). - Respeta
Retry-Aftersi viene. Es lo que el server te dice que ya considera "seguro" para reintentar. - No reintentes 4xx que no sean 429. Reintentar un
422 validation_failedcon el mismo body siempre va a fallar igual. - Una key por integración. Si una integración necesita más de 600 req/min, divide la carga en varias keys con el mismo set de scopes — cada una tendrá su propio bucket.
Subir límites
Durante la beta los límites son fijos. En Fase 4 (Billing) podrás escalar tus tiers desde la UI. Si necesitas más antes, escríbenos.
Ejemplo de backoff
javascript
async function withRetry(fn, maxAttempts = 5) {
let delay = 1000
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const res = await fn()
if (res.status !== 429) return res
const retryAfter = Number(res.headers.get('Retry-After')) || delay / 1000
await new Promise(r => setTimeout(r, retryAfter * 1000))
delay = Math.min(delay * 2, 30000)
}
throw new Error('Max retries exhausted')
}