# Paginación > Recorre los lanzamientos con cursor y next_cursor, las entregas de webhooks con before y has_more, y sincroniza solo lo que ha cambiado. La mayoría de las listas de la API son cortas y llegan completas. Dos listas pueden ser largas y se paginan: los lanzamientos y las entregas de webhooks. ## Qué listas se paginan | Endpoint | Cómo se pagina | Tamaño de página | | --- | --- | --- | | `GET /releases` | `cursor` → `next_cursor` | `limit` 1–100, predeterminado 50 | | `GET /webhooks/{id}/deliveries` | `before` → `has_more` | `limit` 1–100, predeterminado 50 | | `GET /ddex` | No se pagina. Tus últimas 100 entregas. | 100 | | `GET /members` | No se pagina. Todos los artistas y sellos. | Todos | | `GET /links` | No se pagina. Todos los smart links y pre-saves. | Todos | | `GET /payouts` | No se pagina. Todas las solicitudes de retiro. | Todas | | `GET /webhooks` | No se pagina. Como máximo 20 endpoints. | Todos | | `GET /earnings` | No se pagina. Agrupado según `by` durante `months` (1–60). | Todo | ## Lanzamientos: cursores `GET /releases` devuelve los lanzamientos del más reciente al más antiguo. Cada respuesta incluye `next_cursor`. Devuélvelo como `cursor` para obtener la página siguiente. En la última página, `next_cursor` es `null`. ```bash curl "https://api.hillsmusic.com/v1/releases?limit=100" \ -H "Authorization: Bearer $HILLS_API_KEY" # { "data": [ … ], "next_cursor": "aWQ6ODEy" } curl "https://api.hillsmusic.com/v1/releases?limit=100&cursor=aWQ6ODEy" \ -H "Authorization: Bearer $HILLS_API_KEY" ``` Trata el cursor como una cadena opaca. No lo construyas ni lo modifiques tú. Mantén los demás parámetros iguales en todas las páginas. Si filtras la primera página con `status=approved`, envía también `status=approved` con el cursor. Si cambias un filtro, empieza de nuevo sin cursor. ### Todos los lanzamientos, en Node ```js async function* allReleases(params = {}) { let cursor = null; do { const url = new URL("https://api.hillsmusic.com/v1/releases"); for (const [k, v] of Object.entries({ ...params, limit: 100 })) url.searchParams.set(k, v); if (cursor) url.searchParams.set("cursor", cursor); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.HILLS_API_KEY}` }, }); if (!res.ok) throw new Error(`${res.status} ${(await res.json()).code}`); const page = await res.json(); yield* page.data; cursor = page.next_cursor; } while (cursor); } for await (const r of allReleases({ status: "approved" })) { console.log(r.id, r.title, r.delivery_state); } ``` Usa `limit=100` para las lecturas masivas. Reduce a la mitad el número de solicitudes que consumen tu [límite de solicitudes](https://developer.hillsmusic.com/es/guides/rate-limits/). ### Páginas y lanzamientos nuevos Las páginas se cortan por id de lanzamiento. Un lanzamiento creado mientras paginas tiene un id mayor que los que ya has leído, así que solo aparece en una nueva primera página. No te provoca duplicados ni huecos. Un lanzamiento que cambia de estado mientras paginas se queda en su sitio. Si filtras por `status`, un lanzamiento puede salir del filtro entre una página y otra. Para no perderte nada, sincroniza por fecha. ## Sincronizar cambios Para mantener al día tu propia copia del catálogo, no leas la lista completa cada vez. Usa `updated_since`: 1. En la primera ejecución, lee todas las páginas. Anota la hora a la que empezaste, en UTC. 2. En la siguiente ejecución, envía `updated_since` con esa hora y pagina como de costumbre. 3. Guarda la nueva hora de inicio para la ejecución posterior. ```bash curl "https://api.hillsmusic.com/v1/releases?updated_since=2026-10-18T06:00:00Z&limit=100" \ -H "Authorization: Bearer $HILLS_API_KEY" ``` ```js const startedAt = new Date().toISOString(); for await (const r of allReleases({ updated_since: lastRun })) await upsert(r); lastRun = startedAt; // store it ``` `updated_since` devuelve los lanzamientos modificados en la hora que envías o después, así que un lanzamiento modificado justo en el límite llega dos veces. Haz que tus escrituras sean idempotentes por el `id` del lanzamiento. Usar la hora de inicio, y no la de fin, hace que un lanzamiento que cambia durante la ejecución se vuelva a leer la próxima vez. Con los [webhooks](https://developer.hillsmusic.com/es/guides/webhooks/) puedes reaccionar a los cambios en cuanto se producen y dejar `updated_since` como repaso nocturno. ## Entregas de webhooks: before `GET /webhooks/{id}/deliveries` devuelve lo que se envió a un endpoint en los últimos 30 días, de lo más reciente a lo más antiguo. Responde con `has_more`. Cuando es `true`, pasa como `before` el id de la última entrega de la página: ```bash curl "https://api.hillsmusic.com/v1/webhooks/7/deliveries?status=failed&limit=100" \ -H "Authorization: Bearer $HILLS_API_KEY" # { "data": [ …, { "id": 1001, … } ], "has_more": true } curl "https://api.hillsmusic.com/v1/webhooks/7/deliveries?status=failed&limit=100&before=1001" \ -H "Authorization: Bearer $HILLS_API_KEY" ``` ```js async function failedDeliveries(endpointId) { const out = []; let before = null; for (;;) { const url = new URL(`https://api.hillsmusic.com/v1/webhooks/${endpointId}/deliveries`); url.searchParams.set("status", "failed"); url.searchParams.set("limit", "100"); if (before) url.searchParams.set("before", String(before)); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.HILLS_API_KEY}` }, }); const page = await res.json(); out.push(...page.data); if (!page.has_more || !page.data.length) return out; before = page.data.at(-1).id; } } ``` Puedes filtrar por `status` (`pending`, `succeeded` o `failed`) y por `event`. Mantén los mismos filtros en todas las páginas. ## Errores Un cursor que no te haya dado la API responde `400 invalid` con `field: "cursor"`. Un `limit` fuera de 1–100 no se rechaza: se ajusta al intervalo, de modo que `limit=500` da páginas de 100, y un `limit` que no es un número da el valor predeterminado de 50. Consulta [Errores](https://developer.hillsmusic.com/es/guides/errors/). Fuente: https://developer.hillsmusic.com/es/guides/pagination/