Paginación

Todas las listas se recorren por cursor. No hay offset ni page, y no es una omisión: mandarlos es un error explícito.

El sobre

Toda respuesta de lista tiene la misma forma, sin importar de qué recurso sea:

{
  "object": "list",
  "data": [ … ],
  "hasMore": true,
  "nextCursor": "eyJ2IjoxLCJzIjpbImR0IiwiMjAyNi0wOC0wMVQxMDoxMjozM1oiXX0",
  "previousCursor": null,
  "limit": 20,
  "totalCount": null
}

Recorrer hacia adelante

Pide una página, y si hasMore es true vuelve a pedir con startingAfter igual al nextCursor que te dieron. Para ir hacia atrás, endingBefore con el previousCursor. Los dos son excluyentes.

GET /v1/persons?limit=20
GET /v1/persons?limit=20&startingAfter=eyJ2IjoxLCJzIjpb…

El cursor es opaco: lleva dentro el valor de orden y el identificador de la última fila, con su tipo. No lo construyas ni lo interpretes — si lo alteras, recibes INVALID_CURSOR.

El total se pide

totalCount viene en null salvo que mandes includeTotal=true. Contar cuesta un recorrido completo del filtro, y la mayoría de las pantallas no necesitan el número: pídelo solo cuando vayas a enseñarlo, y solo en la primera carga.

Por qué no hay offset

Con offset, si alguien crea un registro mientras recorres la lista, todo se desplaza: te saltas filas o repites otras, sin ningún error de por medio. El cursor ancla en una fila concreta, así que insertar delante no te mueve el suelo.

Y una nota que cuesta una tarde a quien migra: ?offset=2 no se ignora. Devuelve 400. Ignorarlo sería peor — un cliente sin migrar recorrería la lista entera recibiendo siempre la primera página, sin un solo error y con datos incompletos que parecen completos.

Límites

limit va de 1 a 100, con 20 por defecto. Fuera de rango es LIMIT_OUT_OF_RANGE, y el error trae el máximo dentro para que no tengas que buscarlo aquí.