Ir al contenido
HILLS API v1 Acceso anticipado

Guías

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.

Actualizado el

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.

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

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.

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.
curl "https://api.hillsmusic.com/v1/releases?updated_since=2026-10-18T06:00:00Z&limit=100" \
  -H "Authorization: Bearer $HILLS_API_KEY"
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 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:

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"
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.

¿Listo para integrar?

Solicita acceso a la API y te respondemos en 1 a 3 días hábiles.