KicketKicket
Mi cuenta
Guía · Límites de uso▾
  • Inicio
  • Primeros pasos
  • Autenticación
  • Paginación y sincronización
  • Errores
  • Límites de uso
  • Webhooks
  • Conciliación
  • Modelo de datos
  • Referencia
  • Descargas
  • Cambios

API de Kicket · v1

  • Inicio
  • Primeros pasos
  • Autenticación
  • Paginación y sincronización
  • Errores
  • Límites de uso
  • Webhooks
  • Conciliación
  • Modelo de datos
  • Referencia
  • Descargas
  • Cambios

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 credencial300 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/token20 por minuto por credencial desde una misma dirección IP, y 60 por minuto por dirección IP
GET /openapi.jsonSin 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:

HeaderQué diceEjemplo
RateLimit-LimitPedidos por minuto de la credencial.300
RateLimit-RemainingLos que te quedan en este minuto.287
RateLimit-ResetSegundos hasta que el contador vuelve a cero.12
RateLimit-PolicyLa 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.

HTTP/1.1 429 Too Many Requests · Retry-After: 12
{
  "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-After segundos 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_since o 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.

Reintentos con Retry-After
// 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))
  }
}
AnteriorErroresSiguienteWebhooks