REST API · v1

Tài liệu API XuniBOX

API chỉ đọc để lấy danh sách nhạc, nghệ sĩ và thể loại từ kho XuniBOX. Mỗi chủ đề dưới đây là một trang riêng, có đường dẫn riêng để bạn chia sẻ cho đội kỹ thuật.

Ví dụ request và response

Readiness check · GET /readyz

Dùng cho load balancer, Kubernetes readiness probe hoặc hệ thống giám sát: 200 nghĩa là sẵn sàng nhận traffic, 503 kèm reason/reasons và danh sách check khi chưa sẵn sàng. Hỗ trợ cả HEAD.

Request · cURL (GET)
curl --request GET \
  --url 'https://xunibox.vn/api/public/readyz' \
  --header 'Accept: application/json'
Response · 200 OK / 503
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Cache-Control: no-store

{
  "status": "ready",
  "ready": true,
  "api_version": "1.0.0",
  "timestamp": "2026-08-05T15:08:54.464Z",
  "response_time_ms": 24,
  "checks": [
    { "name": "backend_config", "ready": true, "duration_ms": 0, "reason": null },
    { "name": "database_read", "ready": true, "duration_ms": 12, "reason": null },
    { "name": "catalog_read", "ready": true, "duration_ms": 10, "reason": null }
  ]
}

// Khi chưa sẵn sàng:
HTTP/1.1 503 Service Unavailable

{
  "status": "not_ready",
  "ready": false,
  "timestamp": "2026-08-05T15:08:08.988Z",
  "reason": "Cơ sở dữ liệu trả về HTTP 500 khi đọc thử bảng tracks.",
  "reasons": ["Cơ sở dữ liệu trả về HTTP 500 khi đọc thử bảng tracks."]
}

Readiness probe siêu nhẹ · HEAD /readyz

HEAD chạy đúng các check như GET nhưng không trả body — hợp với health check của load balancer, uptime monitor hoặc bước kiểm tra trước khi deploy. Chỉ cần đọc status code: 200 là sẵn sàng, 503 là chưa.

Request · cURL (HEAD)
curl --head \
  --url 'https://xunibox.vn/api/public/readyz'
Response · headers
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: *
Cache-Control: no-store

// Khi chưa sẵn sàng (không có body, chỉ dùng status code):
HTTP/1.1 503 Service Unavailable
Snippet · JavaScript (GET + HEAD)
// Node.js / trình duyệt: kiểm tra readiness trước khi gửi traffic
export async function isXuniBoxReady(timeoutMs = 3000) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const response = await fetch('https://xunibox.vn/api/public/readyz', {
      headers: { Accept: 'application/json' },
      signal: controller.signal,
    });

    const payload = await response.json();
    if (!payload.ready) {
      const failed = payload.checks.filter((check) => !check.ready);
      console.warn('XuniBOX chưa sẵn sàng:', payload.reason, failed);
    }
    return payload.ready === true;
  } catch (error) {
    console.warn('Không gọi được /readyz:', error);
    return false;
  } finally {
    clearTimeout(timer);
  }
}

// Probe siêu nhẹ cho load balancer: chỉ cần status code, không tải body
export async function readyProbe() {
  const response = await fetch('https://xunibox.vn/api/public/readyz', { method: 'HEAD' });
  return response.status === 200;
}
Snippet · Kubernetes & shell
# Kubernetes readiness probe
readinessProbe:
  httpGet:
    path: /api/public/readyz
    port: 443
    scheme: HTTPS
  initialDelaySeconds: 5
  periodSeconds: 30
  timeoutSeconds: 3

# Shell: dừng deploy nếu upstream chưa sẵn sàng
if [ "$(curl -s -o /dev/null -w '%{http_code}' -I https://xunibox.vn/api/public/readyz)" != "200" ]; then
  echo "XuniBOX chưa sẵn sàng, tạm hoãn gửi traffic" && exit 1
fi

Health check · GET /healthz

Dùng để kiểm tra kết nối khi tích hợp. Không cần API key, không tốn hạn mức rate limit.

Request · cURL
curl --request GET \
  --url 'https://xunibox.vn/api/public/healthz' \
  --header 'Accept: application/json'
Response · 200 OK
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Cache-Control: no-store

{
  "status": "ok",
  "api_version": "1.0.0",
  "response_time_ms": 12,
  "timestamp": "2026-08-05T10:03:00.000Z",
  "db_connected": true,
  "db_response_time_ms": 8
}

1. Danh sách bài nhạc · GET /tracks

Lấy bài mới nhất theo thể loại, có phân trang. meta.total là tổng số bản ghi khớp bộ lọc, không phải số phần tử trong data.

Request · cURL
curl --request GET \
  --url 'https://xunibox.vn/api/public/tracks?genre=Pop&sort=newest&page=1&limit=2' \
  --header 'Accept: application/json' \
  --header 'X-API-Key: xnb_live_your_key'
Response · 200 OK
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Total-Count: 128
X-Page: 1
X-Limit: 2
X-Offset: 0

{
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "title": "Mùa hè rực rỡ",
      "artist": "Minh Anh",
      "genre": "Pop",
      "duration": "3:24",
      "audio_url": "https://...signed-preview-url...",
      "preview_start_seconds": 42,
      "preview_duration_seconds": 30,
      "cover_url": "https://...signed-url...",
      "play_count": 1240,
      "created_at": "2026-08-05T01:30:00.000Z"
    },
    {
      "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "title": "Đêm thành phố",
      "artist": "Hoàng Nam",
      "genre": "Pop",
      "duration": "2:58",
      "audio_url": "https://...signed-preview-url...",
      "preview_start_seconds": 30,
      "preview_duration_seconds": 15,
      "cover_url": null,
      "play_count": 318,
      "created_at": "2026-08-04T09:12:00.000Z"
    }
  ],
  "meta": { "total": 128, "page": 1, "limit": 2 }
}

2. Chi tiết một bài · GET /tracks/:id

Trả thẳng object bài hát, không bọc trong data. Dùng để làm mới audio_url trước khi render video.

Request · cURL
curl --request GET \
  --url 'https://xunibox.vn/api/public/tracks/550e8400-e29b-41d4-a716-446655440000' \
  --header 'X-API-Key: xnb_live_your_key'
Response · 200 OK
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "title": "Tên bài hát",
  "artist": "Tên nghệ sĩ",
  "genre": "Pop",
  "duration": "3:24",
  "audio_url": "https://...signed-preview-url...",
  "preview_start_seconds": 42,
  "preview_duration_seconds": 30,
  "cover_url": "https://...signed-url...",
  "play_count": 1240,
  "created_at": "2026-08-05T01:30:00.000Z"
}
Response · 404
HTTP/1.1 404 Not Found

{
  "error": "Không tìm thấy bài hát."
}

3. Làm mới link nghe thử · GET /tracks/:id/audio-url

Endpoint gọn nhất để lấy link media còn hạn: chỉ trả audio_url, cover_url, độ dài preview và thời điểm hết hạn. Gọi ngay trước khi phát hoặc trước khi render video, không lưu link vào database.

Request · cURL
curl --request GET \
  --url 'https://xunibox.vn/api/public/tracks/550e8400-e29b-41d4-a716-446655440000/audio-url' \
  --header 'X-API-Key: xnb_live_your_key'
Response · 200 OK
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Cache-Control: no-store

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "audio_url": "https://...signed-preview-url...",
  "cover_url": "https://...signed-url...",
  "preview_duration_seconds": 30,
  "media_expires_in": 3600,
  "expires_at": "2026-08-05T11:03:00.000Z"
}

// 400 nếu ID sai định dạng UUID.
// 404 nếu bài hát không tồn tại hoặc chưa có đoạn nghe thử.
Snippet · JavaScript
// Gọi ngay trước khi bấm play hoặc trước khi render video.
// Nhẹ hơn /tracks/:id vì chỉ trả đúng phần media.
async function getFreshAudio(trackId) {
  const res = await fetch(
    `https://xunibox.vn/api/public/tracks/${trackId}/audio-url`,
    { headers: { "X-API-Key": process.env.XUNIBOX_API_KEY } }
  );

  if (res.status === 404) return null;         // bài đã bị gỡ
  if (!res.ok) throw new Error(`XuniBOX ${res.status}`);

  const media = await res.json();
  // Hết hạn sau media_expires_in giây — xin lại nếu vượt quá.
  return media;
}

4. Lấy nhiều bài theo ID · GET /tracks/by-ids

Khôi phục playlist đã lưu chỉ bằng một request thay vì gọi /tracks/:id nhiều lần. Cũng là cách làm mới link media hàng loạt (tối đa 50 ID/lượt).

Request · cURL
curl --request GET \
  --url 'https://xunibox.vn/api/public/tracks/by-ids?ids=550e8400-e29b-41d4-a716-446655440000,7c9e6679-7425-40de-944b-e07fc1f90ae7' \
  --header 'X-API-Key: xnb_live_your_key'
Response · 200 OK
HTTP/1.1 200 OK

{
  "data": [
    { "id": "550e8400-e29b-41d4-a716-446655440000", "title": "Mùa hè rực rỡ", "...": "..." },
    { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "title": "Đêm thành phố", "...": "..." }
  ],
  "meta": { "total": 2, "page": 1, "limit": 50 }
}

// ID không tồn tại sẽ bị bỏ qua, không gây lỗi 404.
// ID trùng lặp được gộp lại, thứ tự trả về bám theo thứ tự bạn gửi lên.

5. Danh sách thể loại · GET /genres

data là mảng chuỗi, không phải mảng object. Dùng để dựng bộ lọc thể loại trong UI của bạn.

Request · cURL
curl --request GET \
  --url 'https://xunibox.vn/api/public/genres?page=1&limit=20' \
  --header 'X-API-Key: xnb_live_your_key'
Response · 200 OK
HTTP/1.1 200 OK

{
  "data": ["Acoustic", "Ballad", "EDM", "Lo-fi", "Pop", "Rap"],
  "meta": { "total": 6, "page": 1, "limit": 20 }
}

6. Danh sách nghệ sĩ · GET /artists

Kèm tiểu sử, ảnh đại diện ký tạm thời và track_count. Lấy id ở đây để gọi tiếp /artists/:id/tracks hoặc lọc /tracks?artist_id=.

Request · cURL
curl --request GET \
  --url 'https://xunibox.vn/api/public/artists?sort=name_asc&page=1&limit=2' \
  --header 'Accept: application/json' \
  --header 'X-API-Key: xnb_live_your_key'
Response · 200 OK
HTTP/1.1 200 OK
X-Total-Count: 24

{
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Hoàng Nam",
      "biography": "Nghệ sĩ indie đến từ Đà Nẵng.",
      "avatar_url": "https://...signed-url...",
      "track_count": 12,
      "created_at": "2026-07-21T04:00:00.000Z",
      "media_expires_in": 3600
    },
    {
      "id": "b3f1c2d4-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
      "name": "Minh Anh",
      "biography": null,
      "avatar_url": null,
      "track_count": 5,
      "created_at": "2026-07-02T08:15:00.000Z",
      "media_expires_in": 3600
    }
  ],
  "meta": { "total": 24, "page": 1, "limit": 2 }
}

7. Nhạc của một nghệ sĩ · GET /artists/:id/tracks

Response có thêm khóa artist ở cấp ngoài cùng, tiện dựng trang nghệ sĩ chỉ bằng một request.

Request · cURL
curl --request GET \
  --url 'https://xunibox.vn/api/public/artists/550e8400-e29b-41d4-a716-446655440000/tracks?sort=popular&limit=20' \
  --header 'X-API-Key: xnb_live_your_key'
Response · 200 OK
HTTP/1.1 200 OK

{
  "artist": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Hoàng Nam",
    "biography": "Nghệ sĩ indie đến từ Đà Nẵng.",
    "avatar_url": "https://...signed-url...",
    "created_at": "2026-07-21T04:00:00.000Z",
    "media_expires_in": 3600
  },
  "data": [ { "id": "...", "title": "...", "...": "..." } ],
  "meta": { "total": 12, "page": 1, "limit": 20 }
}

8. Tìm kiếm · GET /tracks?q=

q quét đồng thời tên bài hát, nghệ sĩ và thể loại. Kết hợp được với genre, artist_id, sort và phân trang. Không có kết quả vẫn là 200 với data rỗng — đừng coi đó là lỗi.

Request · JavaScript
const params = new URLSearchParams({
  q: "mùa hè",          // tìm trong tên bài hát, nghệ sĩ và thể loại
  sort: "popular",
  page: "1",
  limit: "20"
});

const response = await fetch(`https://xunibox.vn/api/public/tracks?${params}`, {
  headers: {
    "X-API-Key": process.env.XUNIBOX_API_KEY,
    "Accept": "application/json"
  }
});

const { data, meta } = await response.json();
Response · 200 OK
HTTP/1.1 200 OK

{
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "title": "Mùa hè rực rỡ",
      "artist": "Minh Anh",
      "genre": "Pop",
      "duration": "3:24",
      "audio_url": "https://...signed-preview-url...",
      "preview_start_seconds": 42,
      "preview_duration_seconds": 30,
      "cover_url": "https://...signed-url...",
      "play_count": 1240,
      "created_at": "2026-08-05T01:30:00.000Z"
    }
  ],
  "meta": { "total": 1, "page": 1, "limit": 20 }
}
Response · không có kết quả
HTTP/1.1 200 OK

{
  "data": [],
  "meta": { "total": 0, "page": 1, "limit": 20 }
}

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

Gửi thông tin sản phẩm để admin XuniBOX xét duyệt và cấp API key.

Xin quyền tích hợp