Paginación y sincronización
Cómo recorrer un listado sin saltarte filas y cómo ponerte al día sin releer el catálogo entero cada vez.
Última actualización: 23 de agosto de 2026
Todos los listados de la API paginan igual y devuelven la misma envoltura:
{
"data": [ "…los elementos…" ],
"hasMore": true,
"nextCursor": "eyJpZCI6IjZhOGE0YzJkOWUxZjNiNWM3ZDBlOGYyYSJ9"
}
datason los elementos de esta página.hasMorete dice si queda algo detrás.nextCursores lo que pides para traer lo siguiente. EsnullcuandohasMoreesfalse.
Parámetros
| Parámetro | Por defecto | Máximo |
|---|---|---|
limit | 25 | 100 |
cursor | — | — |
Un limit por encima de 100 se recorta a 100 en silencio. El tope es duro: sin él, un limit=100000 convierte cualquier listado en una descarga de la colección entera.
Recorrer un listado entero
# Primera página
curl -G https://app.tickeep.com/api/v1/orders \
-H "Authorization: Bearer $TICKEEP_API_KEY" \
--data-urlencode "limit=100"
# Siguientes
curl -G https://app.tickeep.com/api/v1/orders \
-H "Authorization: Bearer $TICKEEP_API_KEY" \
--data-urlencode "limit=100" \
--data-urlencode "cursor=eyJpZCI6IjZhOGE0YzJkOWUxZjNiNWM3ZDBlOGYyYSJ9"
async function* todosLosPedidos(venueId: string) {
let cursor: string | undefined
do {
const pagina = await tickeep.orders({ venueId, limit: 100, cursor })
yield* pagina.data
cursor = pagina.nextCursor ?? undefined
} while (cursor)
}
Los filtros hay que repetirlos en cada página. El cursor guarda la posición, no la consulta.
Por qué cursor y no números de página
Es la duda que aparece siempre, así que aquí está el motivo con un caso concreto.
Imagina que recorres tus pedidos con ?page=1&perPage=25, ordenados de más reciente a más antiguo. Lees la página 1: los pedidos del 1 al 25. Mientras procesas esa página, entran tres ventas nuevas. Pides la página 2, que ahora devuelve los pedidos del 26 al 50 de una lista que se ha desplazado tres posiciones.
Resultado: los pedidos que antes ocupaban las posiciones 23, 24 y 25 ahora están en la 26, 27 y 28, y ya los tenías. Pero los que estaban en la 26, 27 y 28 han pasado a la 29, 30 y 31 — y no los ves nunca. Se han caído entre dos páginas, sin ningún error, sin nada en los logs, sin que tu código se entere.
El cursor no tiene ese problema porque no cuenta posiciones: apunta al último elemento que te llevaste y la página siguiente empieza justo después de él. Si entran ventas nuevas, aparecen donde les toque; lo que ya viste no se mueve.
Es una cadena que devuelves tal cual. Hoy lleva dentro un identificador codificado en base64, pero eso puede cambiar sin aviso: es justo el motivo de que sea opaco. No lo descodifiques, no lo construyas a mano, no supongas nada de su contenido. Guárdalo como cadena y ya está.
Un cursor corrupto o inventado se trata como «sin cursor» y devuelve la primera página, en vez de dar un error. Es deliberado: es algo que un cliente correcto nunca compone a mano.
Orden de cada listado
| Listado | Orden |
|---|---|
/events | Más reciente primero |
/performances | Cronológico ascendente (la próxima fecha primero) |
/orders | Más reciente primero |
/tickets | Más reciente primero |
/attendees | Más reciente primero |
/venues | Alfabético por nombre, sin paginación |
/performances es el único que va al revés, y es lo que quieres: un calendario se lee hacia delante.
Sincronización incremental
Releerte el catálogo entero cada cinco minutos es tirar peticiones. Tres listados aceptan updatedSince para traerte solo lo que ha cambiado:
/events?updatedSince=2026-08-23T10:00:00ZEventos modificados después de esa fecha.
/orders?updatedSince=2026-08-23T10:00:00ZPedidos modificados después de esa fecha. Incluye cambios de estado.
/attendees?updatedSince=2026-08-23T10:00:00ZAsistentes modificados después de esa fecha.
El valor es una fecha ISO 8601. La comparación es estricta: updatedSince devuelve lo modificado después de ese instante, no en él.
El patrón que funciona
// Guardado de la última pasada
let marca = await db.leer('tickeep:ultima_sincronizacion')
let cursor: string | undefined
let maxVisto = marca
do {
const pagina = await tickeep.orders({ updatedSince: marca, limit: 100, cursor })
for (const pedido of pagina.data) {
await miCrm.upsert(pedido)
if (pedido.updatedAt > maxVisto) maxVisto = pedido.updatedAt
}
cursor = pagina.nextCursor ?? undefined
} while (cursor)
await db.guardar('tickeep:ultima_sincronizacion', maxVisto)
Tres detalles que evitan los fallos típicos:
Guarda la marca al final, no al principio. Si la pasada se rompe a medias, la siguiente vuelve a empezar desde donde había llegado la última completa. Reprocesar un pedido no cuesta nada; saltárselo, sí.
Usa el updatedAt máximo que has visto, no la hora de tu reloj. Tu servidor y el nuestro no tienen por qué estar sincronizados al milisegundo, y una desviación de dos segundos en la dirección mala se come registros.
Haz tu escritura idempotente. Un upsert por id, no un insert. La ventana se solapa siempre un poco, y volverás a ver cosas que ya tenías.
updatedSince es una red de seguridad excelente y un mal sistema de notificación: entre dos pasadas hay minutos. Si necesitas reaccionar a una venta al momento, escucha el webhook order.paid y deja la sincronización periódica para cuadrar lo que se haya podido perder. Las dos cosas juntas, no una de las dos.
Filtros por listado
Además de limit y cursor, cada listado tiene los suyos:
| Listado | Filtros |
|---|---|
/events | venueId, status, updatedSince |
/performances | eventId, venueId, from, to |
/orders | venueId, status, email, updatedSince |
/tickets | venueId, performanceId, eventId, orderId, status |
/attendees | email, updatedSince |
Los detalles de cada uno están en las páginas de referencia. Todos se combinan con and: si mandas venueId y status, se aplican los dos.
Seguir por aquí
Formatos, nombres, fechas, dinero y cabeceras: lo que se repite en todas las respuestas y no vuelve a explicarse en cada endpoint.
Llevar pedidos y asistentes a tu CRM, tu cuadro de mando o tu contabilidad, sin perder registros ni releerlo todo cada vez.
Que Tickeep avise a tu servidor cuando pasa algo, en vez de preguntar en bucle. Eventos, firma, reintentos y los tres fallos que se cometen siempre.