Chuyển đến nội dung
HILLS API v1

Hướng dẫn

Phân trang

Duyệt bản phát hành theo trang với cursor và next_cursor, duyệt các lần gửi webhook với before và has_more, và chỉ đồng bộ phần đã thay đổi.

Cập nhật

Hầu hết danh sách trong API đều ngắn và được trả về trọn vẹn. Có hai danh sách có thể dài và được phân trang: bản phát hành và các lần gửi webhook.

Danh sách nào được phân trang

Endpoint Cách phân trang Kích thước trang
GET /releases cursor → next_cursor limit 1–100, mặc định 50
GET /webhooks/{id}/deliveries before → has_more limit 1–100, mặc định 50
GET /ddex Không phân trang. 100 lần phân phối gần nhất của bạn. 100
GET /members Không phân trang. Mọi nghệ sĩ và hãng đĩa. Tất cả
GET /links Không phân trang. Mọi smart link và pre-save. Tất cả
GET /payouts Không phân trang. Mọi yêu cầu rút tiền. Tất cả
GET /webhooks Không phân trang. Tối đa 20 endpoint. Tất cả
GET /earnings Không phân trang. Nhóm theo by trong khoảng months (1–60). Tất cả

Bản phát hành: cursor

GET /releases trả về bản phát hành mới nhất trước. Mỗi phản hồi có next_cursor. Hãy gửi lại giá trị này trong cursor để lấy trang tiếp theo. Ở trang cuối, next_cursor là 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"

Hãy coi cursor là một chuỗi không cần hiểu nội dung (opaque). Đừng tự tạo hay sửa nó.

Giữ nguyên các parameter khác trên mọi trang. Nếu bạn lọc trang đầu bằng status=approved, hãy gửi cả status=approved cùng với cursor. Nếu đổi bộ lọc, bạn nên bắt đầu lại từ đầu mà không có cursor.

Lấy mọi bản phát hành bằng 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);
}

Dùng limit=100 khi đọc số lượng lớn. Cách này giảm một nửa số request tính vào rate limit của bạn.

Trang và bản phát hành mới

Trang được chia theo id bản phát hành. Một bản phát hành được tạo trong lúc bạn đang duyệt trang có id cao hơn những bản bạn đã đọc, nên nó chỉ xuất hiện ở một trang đầu mới. Bạn sẽ không bị trùng lặp hay bỏ sót vì điều đó.

Một bản phát hành đổi trạng thái trong lúc bạn duyệt trang vẫn giữ nguyên vị trí. Nếu bạn lọc theo status, một bản phát hành có thể rời khỏi bộ lọc giữa hai trang. Để không bỏ sót gì, hãy đồng bộ theo thời gian.

Đồng bộ thay đổi

Để giữ bản sao danh mục của riêng bạn luôn cập nhật, đừng đọc toàn bộ danh sách mỗi lần. Hãy dùng updated_since:

  1. Ở lần chạy đầu tiên, đọc mọi trang. Ghi lại thời điểm bắt đầu, theo UTC.
  2. Ở lần chạy tiếp theo, gửi updated_since bằng thời điểm đó, và phân trang như bình thường.
  3. Lưu thời điểm bắt đầu mới cho lần chạy sau.
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 khớp với các bản phát hành thay đổi tại hoặc sau thời điểm bạn gửi, nên một bản phát hành thay đổi đúng ở ranh giới sẽ được trả về hai lần. Hãy làm cho thao tác ghi của bạn idempotent theo id của bản phát hành.

Dùng thời điểm bắt đầu thay vì thời điểm kết thúc nghĩa là một bản phát hành thay đổi trong lúc chạy sẽ được đọc lại ở lần sau. Với webhook, bạn có thể phản ứng với thay đổi ngay khi nó xảy ra, và giữ updated_since làm lượt đồng bộ bù mỗi đêm.

Lần gửi webhook: before

GET /webhooks/{id}/deliveries trả về những gì đã được gửi đến một endpoint trong 30 ngày gần nhất, mới nhất trước. Phản hồi có has_more. Khi giá trị này là true, hãy gửi id của lần gửi cuối cùng trên trang trong 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;
  }
}

Bạn có thể lọc theo status (pending, succeeded hoặc failed) và event. Giữ nguyên bộ lọc trên mọi trang.

Lỗi

Một cursor không phải do API cấp sẽ trả về 400 invalid với field: "cursor". Một limit nằm ngoài khoảng 1–100 không bị từ chối mà được đưa về trong khoảng, nên limit=500 cho trang 100 phần tử, và một limit không phải số sẽ dùng giá trị mặc định 50. Xem Lỗi.

Sẵn sàng tích hợp?

Đăng ký quyền truy cập API, chúng tôi sẽ phản hồi bạn trong vòng 1–3 ngày làm việc.