Pular para o conteúdo
HILLS API v1 Acesso antecipado

Guias

Paginação

Pagine lançamentos com cursor e next_cursor, pagine entregas de webhook com before e has_more, e sincronize só o que mudou.

Atualizado em

A maioria das listas da API é curta e vem inteira. Duas listas podem ser longas e são paginadas: lançamentos e entregas de webhook.

Quais listas são paginadas

Endpoint Como pagina Tamanho da página
GET /releases cursor → next_cursor limit de 1 a 100, padrão 50
GET /webhooks/{id}/deliveries before → has_more limit de 1 a 100, padrão 50
GET /ddex Não pagina. Suas últimas 100 entregas. 100
GET /members Não pagina. Todos os artistas e gravadoras. Tudo
GET /links Não pagina. Todos os smartlinks e pré-saves. Tudo
GET /payouts Não pagina. Todas as solicitações de saque. Tudo
GET /webhooks Não pagina. No máximo 20 endpoints. Tudo
GET /earnings Não pagina. Agrupado por by ao longo de months (1 a 60). Tudo

Lançamentos: cursores

GET /releases retorna os lançamentos dos mais recentes para os mais antigos. Cada resposta tem next_cursor. Envie-o de volta como cursor para obter a próxima página. Na última página, next_cursor é 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"

Trate o cursor como uma string opaca. Não o monte nem o altere por conta própria.

Mantenha os outros parâmetros iguais em todas as páginas. Se você filtrou a primeira página com status=approved, envie status=approved junto com o cursor também. Se mudar um filtro, recomece sem cursor.

Todos os lançamentos, em 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);
}

Use limit=100 para leituras em massa. Isso reduz pela metade o número de requisições contadas no seu limite de requisições.

Páginas e lançamentos novos

As páginas são divididas pelo id do lançamento. Um lançamento criado enquanto você pagina tem um id maior do que os que você já leu, então ele só aparece em uma nova primeira página. Isso não gera duplicatas nem lacunas.

Um lançamento que muda de status enquanto você pagina continua no mesmo lugar. Se você filtra por status, um lançamento pode sair do filtro entre uma página e outra. Para não perder nada, sincronize por horário.

Sincronização de mudanças

Para manter a sua própria cópia do catálogo atualizada, não leia a lista inteira toda vez. Use updated_since:

  1. Na primeira execução, leia todas as páginas. Anote o horário em que começou, em UTC.
  2. Na execução seguinte, envie updated_since com esse horário e pagine normalmente.
  3. Guarde o novo horário de início para a próxima execução.
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 retorna os lançamentos alterados no horário enviado ou depois dele, então um lançamento alterado exatamente no limite volta duas vezes. Torne suas gravações idempotentes pelo id do lançamento.

Usar o horário de início, e não o de término, faz com que um lançamento alterado durante a execução seja lido de novo na próxima. Com webhooks você reage às mudanças assim que elas acontecem e mantém updated_since como uma conferência noturna.

Entregas de webhook: before

GET /webhooks/{id}/deliveries retorna o que foi enviado a um endpoint nos últimos 30 dias, das mais recentes para as mais antigas. A resposta traz has_more. Quando ele é true, envie o id da última entrega da página como before:

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;
  }
}

Você pode filtrar por status (pending, succeeded ou failed) e por event. Mantenha os filtros iguais em todas as páginas.

Erros

Um cursor que não foi gerado pela API responde 400 invalid com field: "cursor". Um limit fora do intervalo de 1 a 100 não é recusado: ele é ajustado ao intervalo, então limit=500 gera páginas de 100, e um limit que não é número usa o padrão de 50. Veja Erros.

Pronto para integrar?

Solicite acesso à API e retornamos em 1 a 3 dias úteis.