Quy ước chung

Phân trang, chống trùng, định dạng lỗi và cách đọc ngày giờ — giống nhau ở mọi endpoint.

Hình dạng phản hồi

Endpoint trả về một tài nguyên thì đưa thẳng đối tượng đó, không bọc trong lớp data nào:

{
  "id": "0c2f…",
  "title": "Bản tin sáng",
  "status": "RENDERING",
  "targets": [ … ]
}

Endpoint trả về một danh sách thì bọc trong khuôn chung, và các phần tử nằm ở data:

{
  "object": "list",
  "url": "/api/v1/public/videos",
  "data": [ … ],
  "hasMore": true,
  "totalCount": 137,
  "page": 1,
  "pageSize": 25
}

Khoá JSON viết kiểu camelCase, tên header viết kiểu kebab-case, còn mã lỗi viết kiểu snake_case. Mỗi phản hồi mang theo header x-request-id — gửi kèm giá trị đó khi báo sự cố thì tra log nhanh hơn nhiều.

Phân trang

Hai kiểu, tuỳ dữ liệu có bị chèn thêm liên tục hay không.

KiểuTham sốDùng ởPhản hồi
Theo số trangpage, pageSizevideos, campaigns, sources, channels, templatestotalCount, page, pageSize, hasMore
Theo con trỏlimit, startingAftermediahasMore — không có tổng số

pageSize mặc định 25, trần 100. Kho media dùng con trỏ vì tệp mới được thêm liên tục: đánh số trang thì vừa lật vừa bị đẩy, một tệp hiện ở hai trang hoặc biến mất hẳn.

Chống trùng khi tạo

Các endpoint tiêu tiền bắt buộc gửi header Idempotency-Key: tạo video, xếp việc sinh nội dung, tạo chiến dịch, tạo nguồn tin, dựng lại và đăng.

Idempotency-Key: 8f3b-tao-video-ban-tin-2026-09-22
  • Chuỗi do bạn tự sinh, 8–255 ký tự. Một khoá cho một ý định.
  • Gửi lại đúng khoá cũ kèm đúng thân yêu cầu cũ sẽ nhận lại nguyên kết quả lần đầu, kể cả mã trạng thái — không tạo thêm video, không trừ thêm credit.
  • Gửi lại đúng khoá cũ nhưng thân yêu cầu khác sẽ bị từ chối với mã 422. Đó là dấu hiệu mã của bạn dùng lại khoá cho một việc khác.
  • Lượt trước còn đang chạy thì lượt sau nhận 409. Chờ vài giây rồi hỏi lại bằng chính khoá đó, đừng đổi khoá mới.
  • Khoá tính riêng theo từng endpoint và có hiệu lực 24 giờ.

Mất kết nối giữa chừng thì cứ gửi lại đúng khoá đó

Đó chính là việc header này sinh ra để làm. Sinh khoá mới cho lần thử lại là tự tạo ra video thứ hai và một lượt trừ credit thứ hai.

Định dạng lỗi

Lỗi trả về theo chuẩn RFC 9457 với content-type: application/problem+json:

{
  "type": "https://…/errors/insufficient-credits",
  "title": "Unprocessable Entity",
  "status": 422,
  "detail": "Số dư credit không đủ cho thao tác này",
  "instance": "/api/v1/public/videos",
  "code": "insufficient_credits",
  "requestId": "req-01HXY7K3MN8P2RZ4QW9TB6FH3D",
  "timestamp": "2026-09-22T02:00:00.000Z"
}

requestId trùng với header x-request-id — gửi kèm giá trị đó khi báo sự cố. Lỗi kiểm tra dữ liệu có thêm mảng errors, mỗi phần tử nêu path, code và message của đúng một trường sai.

So sánh bằng `code`, đừng so bằng `detail`

detail là câu tiếng Việt hoặc tiếng Anh, sửa lúc nào cũng được và đổi theo ngôn ngữ người gọi. code mới là giá trị ổn định để rẽ nhánh.

Mã HTTPThường gặp khi
400 / 422Thân yêu cầu thiếu trường, sai kiểu, hoặc không đủ credit
401Thiếu khoá hoặc khoá không dùng được
403Khoá thiếu quyền cho đường này
404Id không tồn tại, hoặc không thuộc tài khoản của khoá
409Lượt trước với cùng khoá chống trùng vẫn đang chạy
429Gọi quá nhanh

Ngày giờ

  • Mọi mốc thời gian trả về là chuỗi ISO 8601 theo giờ UTC, ví dụ 2026-09-22T02:00:00.000Z.
  • Khi gửi lên cũng dùng ISO 8601. Kèm phần lệch múi giờ để không phụ thuộc vào máy chủ đang đặt ở đâu.
  • Riêng nguồn tin và chiến dịch có trường timezone dạng tên IANA (ví dụ Asia/Ho_Chi_Minh) vì lịch quét và lịch đăng tính theo giờ địa phương của bạn.

Giới hạn tốc độ

Hệ thống có trần số lượt gọi trong một khoảng thời gian. Vượt trần trả về 429; khi đó hãy giãn nhịp và thử lại với khoảng chờ tăng dần thay vì bắn lại ngay.

Ngoài trần chung còn có trần riêng ở phía nhà cung cấp mô hình AI. Bắn hàng chục lượt tạo video song song không làm mọi thứ nhanh hơn — chúng xếp hàng ở khâu dựng, và lượt dồn dập dễ hỏng giữa chừng hơn.