Límites de uso
300 pedidos por minuto por credencial. Más que suficiente para sincronizar una cadena entera.
El límite
| Qué | Límite |
|---|---|
| Cualquier ruta de la API, por credencial | 300 pedidos por minuto |
| Cualquier ruta de la API, por cuenta (sumando todas sus credenciales) | 600 pedidos por minuto |
| Los pedidos pesados, por cuenta: el reporte diario, /sales con status o channel, /payments con method_id o created_* | 60 pedidos por minuto |
POST /oauth/token | 20 por minuto por credencial desde una misma dirección IP, y 60 por minuto por dirección IP |
GET /openapi.json | Sin límite propio (es público y va con caché) |
El contador es por minuto de reloj: vuelve a cero al empezar cada minuto. Los tokens de una misma credencial comparten el contador de la credencial; todas las credenciales de la cuenta comparten el de la cuenta (crear más credenciales no da más cupo). Los headers RateLimit-* son los de la credencial; si el que se agotó es el de la cuenta, el 429 lo dice en el detail. El de POST /oauth/token va con la dirección IP: alguien que conozca tu client_id e insista desde otra red no te traba el token.
Los headers
Toda respuesta autenticada dice cómo vas:
| Header | Qué dice | Ejemplo |
|---|---|---|
RateLimit-Limit | Pedidos por minuto de la credencial. | 300 |
RateLimit-Remaining | Los que te quedan en este minuto. | 287 |
RateLimit-Reset | Segundos hasta que el contador vuelve a cero. | 12 |
RateLimit-Policy | La regla: tantos pedidos en una ventana de tantos segundos. | 300;w=60 |
Si te pasás: 429
La respuesta es 429 con el código rate_limited y el header Retry-After: los segundos que faltan para que puedas volver a pedir.
{
"type": "https://kicket.club/desarrolladores/errores#rate_limited",
"title": "Demasiados pedidos",
"status": 429,
"detail": "Pasaste el límite de 300 pedidos por minuto de esta credencial. Reintentá en 12 s.",
"code": "rate_limited",
"request_id": "req_9b2e4c1a7f3d5e8a0b6c2d41"
}- Esperá
Retry-Aftersegundos y reintentá el mismo pedido. No reintentes antes: el pedido vuelve a contar. - Si te pasa seguido, bajá el paralelismo (menos pedidos a la vez) y usá
limit=500: menos páginas, menos pedidos. - Para no consultar a cada rato, sincronizá cada pocos minutos con
updated_sinceo escuchá los webhooks.
Un reintento bien hecho
El cliente mínimo de la guía ya lo hace: con 429 espera lo que dice Retry-After; con un 5xx, una espera creciente (2, 4, 8… segundos), con un tope de intentos.
// kicket.mjs — un cliente mínimo, sin dependencias (Node 18 o más nuevo).
const BASE = process.env.KICKET_API_URL ?? 'https://kicket.club/api/v1'
let token = null // { valor, venceEn }
async function pedirToken(intento = 1) {
const basic = Buffer.from(`${process.env.KICKET_CLIENT_ID}:${process.env.KICKET_CLIENT_SECRET}`).toString('base64')
const res = await fetch(`${BASE}/oauth/token`, {
method: 'POST',
headers: { Authorization: `Basic ${basic}`, 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ grant_type: 'client_credentials' }),
})
// 429: muchos pedidos de token seguidos (por ejemplo, varios procesos a la vez). Esperá lo que dice Retry-After.
if (res.status === 429 && intento < 5) {
await new Promise((r) => setTimeout(r, (Number(res.headers.get('Retry-After')) || 10) * 1000))
return pedirToken(intento + 1)
}
if (!res.ok) throw new Error(`No se pudo pedir el token: ${res.status} ${await res.text()}`)
const { access_token, expires_in } = await res.json()
// Se renueva un minuto antes de que venza: un token por hora, no uno por pedido.
token = { valor: access_token, venceEn: Date.now() + (expires_in - 60) * 1000 }
return token.valor
}
export async function llamar(ruta, { intentos = 5 } = {}) {
const url = ruta.startsWith('http') ? ruta : `${BASE}${ruta}`
for (let intento = 1; ; intento++) {
const valor = token && Date.now() < token.venceEn ? token.valor : await pedirToken()
const res = await fetch(url, { headers: { Authorization: `Bearer ${valor}` } })
if (res.ok) return res.json()
// 401: el token venció o se rotó el secreto. Uno nuevo y otra vez (una sola).
if (res.status === 401 && intento === 1) {
token = null
continue
}
const reintentable = res.status === 429 || res.status >= 500
if (!reintentable || intento >= intentos) {
const p = await res.json().catch(() => ({}))
throw new Error(`${res.status} ${p.code ?? ''}: ${p.detail ?? res.statusText} (request_id ${p.request_id ?? '?'})`)
}
// 429 trae Retry-After; para un 5xx, espera creciente (2, 4, 8… segundos).
const espera = Number(res.headers.get('Retry-After')) || Math.min(60, 2 ** intento)
await new Promise((r) => setTimeout(r, espera * 1000))
}
}