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:
- Ở lần chạy đầu tiên, đọc mọi trang. Ghi lại thời điểm bắt đầu, theo UTC.
- Ở lần chạy tiếp theo, gửi
updated_sincebằng thời điểm đó, và phân trang như bình thường. - 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.