88API88API
User GuideAI ApplicationsAPI ReferenceHelp & Support
Video

Quy chuẩn gọi Video API đầy đủ

Quy chuẩn thống nhất cho API tạo video của 88API.

Trang này là quy chuẩn tạo video thống nhất mà 88API công bố cho khách hàng, áp dụng cho mọi model video do 88API cung cấp. Khách hàng chỉ cần lấy ID model từ danh sách model mà API Key của mình nhìn thấy, rồi dùng giao diện thống nhất trên trang này.

Hiện tại, mọi tác vụ video dùng chung giao thức “lấy model → gửi → truy vấn định kỳ → tải xuống”. Các tài khoản khác nhau có thể thấy ID model khác nhau; thời lượng, tỷ lệ khung hình, độ phân giải và số lượng tư liệu tham chiếu mà mỗi model hỗ trợ cũng có thể khác nhau. Đừng áp khả năng của model này cho model khác.

Cập nhật ngày 2026-10-10. Tham số và ví dụ riêng của từng thương hiệu đã được tách thành các trang độc lập, xem Quy chuẩn theo từng thương hiệu bên dưới. Quy trình khuyến nghị: trước tiên gọi GET /v1/models để lấy ID model mà Key hiện tại được dùng; tư liệu HTTPS đã có sẵn thì tham chiếu trực tiếp; tư liệu cục bộ thì tải lên trước → gửi tác vụ tạo video → truy vấn định kỳ để lấy URL kết quả → phát, tải xuống hoặc dùng làm tham chiếu cho lượt tiếp theo. Tải lên thành công không có nghĩa là model chắc chắn hỗ trợ loại tư liệu đó; khả năng của model và khả năng lưu trữ là hai chuyện khác nhau.

Quy tắc tích hợp thống nhất: trước tiên điền model bằng id chính xác do GET /v1/models trả về, sau đó tạo tác vụ bằng POST /v1/videos, lưu id trong phản hồi và truy vấn định kỳ qua GET /v1/videos/{id}. Tích hợp mới không dùng /v1/video/generations cũ, không phụ thuộc vào task_id, và không coi callback_url là khả năng mà mọi model đều hỗ trợ; khi cần tương thích ổn định, hãy thống nhất dùng truy vấn định kỳ.

1. Quy tắc tối thiểu để tích hợp thống nhất

Nếu cùng một client cần tích hợp nhiều model video, chỉ cần cố định quy trình sau:

  1. Gọi GET /v1/models bằng API Key hiện tại.
  2. Chọn id của model mà khách hàng thực sự có quyền từ kết quả trả về; không mã hóa cứng tên trong trang model, tên cũ hoặc tên mà tài khoản khác nhìn thấy vào cấu hình toàn cục.
  3. Gửi tác vụ bằng cùng một định dạng yêu cầu POST /v1/videos.
  4. Chỉ truy vấn tác vụ bằng id công khai trong phản hồi; không phân tích hay ghép bất kỳ ID tác vụ nào khác.
  5. Lấy video qua url, video_url hoặc result_url trong phản hồi hoàn tất.

Các trường chung được khuyến nghị:

Mục đíchTrường khuyến nghịGiải thích
ModelmodelPhải là ID model chính xác mà Key hiện tại lấy từ /v1/models
Văn bảnpromptThường bắt buộc với tạo video chỉ từ văn bản; có được bỏ trống hay không tùy model
Thời lượngduration hoặc secondsChọn một trong hai, đơn vị giây; ưu tiên duration
Tỷ lệ khung hìnhratio hoặc aspect_ratioKhuyến nghị dùng để đặt tỷ lệ khung hình; giá trị dùng được tùy theo khả năng của model đích
Ảnh tham chiếuimagesKhuyến nghị dùng URL HTTPS công khai hoặc URL assets.88api.ai đã tải lên
Video tham chiếumetadata.referenceVideosChỉ dùng khi model đích hỗ trợ
Âm thanh tham chiếumetadata.referenceAudiosChỉ dùng khi model đích hỗ trợ
Khung hình đầu và cuốimetadata.firstFrame, metadata.lastFrameĐồng thời phải tuân thủ quy tắc khung hình đầu/cuối và quy tắc loại trừ tư liệu của model đích

Bảng trên là cách viết chung cho phần lớn model. Một số thương hiệu dùng trường khác (ví dụ với Wan, khung hình đầu, khung hình cuối và tư liệu tham chiếu nên dùng mảng media; trường tư liệu không đọc được sẽ trả lỗi 400); hãy theo Quy chuẩn theo từng thương hiệu.

size là trường tương thích mà một số model và client cũ sử dụng. Client mới nên ưu tiên ratio hoặc aspect_ratio và chọn giá trị cụ thể theo mô tả khả năng của model đích.

Trong các ví dụ tiếp theo của trang này, <MODEL_ID> đều là ID model thực lấy từ /v1/models theo các bước trên, không phải một chuỗi cố định mà mọi khách hàng phải dùng chung.

2. Tra cứu nhanh endpoint

Mục đíchPhương thức và đường dẫnGiải thích
Lấy modelGET https://api.88api.ai/v1/modelsTrả về ID model mà API Key hiện tại nhìn thấy; lấy kết quả trả về làm chuẩn
Gửi yêu cầu tạoPOST https://api.88api.ai/v1/videosGửi văn bản và tư liệu tham chiếu, trả về id tác vụ
Truy vấn tác vụGET https://api.88api.ai/v1/videos/{id}Truy vấn trạng thái, tiến độ và lỗi
Xin tải lên tư liệuPOST https://api.88api.ai/v1/media/uploadsGửi mã băm, số byte và MIME của tệp, nhận quyền tải lên ngắn hạn
Tải lên dữ liệu nhị phânPUT {upload_url}Dùng header tải lên được cấp, không gửi API Key
Lấy videoGET {url}Ưu tiên URL kết quả trong phản hồi hoàn tất, không tự ghép đường dẫn
Lối vào nội dung cũGET https://api.88api.ai/v1/videos/{id}/contentChuyển hướng tương thích cho video đã lưu trữ; không phải cách tải xuống chung cho mọi kết quả
Callback trạng thái cuốiĐặt callback_url khi gửiChỉ cho model hỗ trợ callback; các model khác dùng truy vấn định kỳ

Header yêu cầu

Authorization: Bearer sk-API-Key-của-bạn
Content-Type: application/json
Accept: application/json

API Key phải được lưu ở phía máy chủ, không được đưa vào gói frontend của trình duyệt, mini program hay ứng dụng.

Thao tácXác thực
Tạo, truy vấn tác vụ; xin quyền tải lênAuthorization: Bearer ..., chỉ gửi tới API api.88api.ai
Tải tệp lênDùng nguyên X-Media-Upload-Token và Content-Type trong headers
Đọc tư liệu công khaiĐịa chỉ assets.88api.ai không cần Bearer, Cookie hay đăng nhập

Đọc ẩn danh không có nghĩa là tải lên ẩn danh, cũng không công khai chi tiết tác vụ. Ai có URL tư liệu công khai đều có thể đọc tệp trong thời hạn hiệu lực; đừng coi URL là cơ chế cách ly quyền riêng tư nghiêm ngặt. Không chuyển tiếp API Key của bạn tới địa chỉ tư liệu, địa chỉ kết quả chính thức hay đích chuyển hướng.

Lấy ID model mà tài khoản hiện tại được dùng

ID model không cố định giống nhau cho mọi tài khoản. Khi client tích hợp lần đầu hoặc làm mới danh sách model định kỳ, hãy chạy trước:

curl 'https://api.88api.ai/v1/models' \
  --header 'Authorization: Bearer sk-xxxx'

Chọn model từ data[].id trong phản hồi và đặt nguyên giá trị đó vào trường model của các yêu cầu POST /v1/videos tiếp theo. Không tự ghép ID theo tên hiển thị của model, cũng không coi tên model từ tài khoản khác hoặc tài liệu cũ là chắc chắn dùng được với tài khoản hiện tại.

{
  "object": "list",
  "data": [
    {
      "id": "<MODEL_ID>",
      "object": "model",
      "owned_by": "88api"
    }
  ]
}

3. Yêu cầu gửi tiêu chuẩn

Yêu cầu tạo video từ văn bản tối thiểu

curl --request POST 'https://api.88api.ai/v1/videos' \
  --header 'Authorization: Bearer sk-xxxx' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "model": "<MODEL_ID>",
    "prompt": "Siêu xe màu bạc trong thành phố đêm mưa, máy quay góc thấp bám theo, phản chiếu ánh đèn neon, chất điện ảnh",
    "duration": 8,
    "ratio": "16:9"
  }'

duration cũng có thể viết thành seconds dạng chuỗi:

{
  "model": "<MODEL_ID>",
  "prompt": "Flycam bờ biển lúc bình minh, mây trôi chậm",
  "seconds": "8",
  "ratio": "16:9"
}

Các trường yêu cầu chung

TrườngKiểuBắt buộcGiải thích
modelstringCóID model chính xác mà API Key hiện tại lấy qua GET /v1/models
promptstringCó điều kiệnPrompt văn bản; chỉ một số model cho phép bỏ trống khi có tư liệu tham chiếu
negative_promptstringKhôngPrompt phủ định cho model đa tham chiếu; với Veo cũng có thể viết là metadata.negativePrompt
durationintegerKhôngThời lượng video, đơn vị giây; chọn một trong hai với seconds
secondsstringKhôngThời lượng video dạng chuỗi, ví dụ "8"
ratiostringKhôngTrường tỷ lệ khung hình khuyến nghị, ví dụ 16:9, 9:16
aspect_ratiostringKhôngBí danh tương thích của ratio; giá trị dùng được tùy theo khả năng của model đích
sizestringKhôngTrường tương thích; tích hợp mới không dựa vào nó để truyền tỷ lệ khung hình
resolutionstringKhôngChỉ dùng khi model đích hỗ trợ, ví dụ 480p, 720p, 1080p, 4k
imagestringKhôngURL của một ảnh tham chiếu
imagesstring[]KhôngMảng URL ảnh tham chiếu, cách dùng khuyến nghị
videostringKhôngURL của một video tham chiếu; Gemini Omni bắt buộc dùng trường cấp gốc này
videosstring[]KhôngMảng video tham chiếu; các model đa tham chiếu khác dùng metadata.referenceVideos
seedintegerKhôngHạt giống ngẫu nhiên
generateAudio / generate_audiobooleanKhôngBật tạo âm thanh đồng bộ trên model hỗ trợ; chỉ dùng khi khả năng của model cho phép
camera_controlobjectKhôngĐiều khiển máy quay; chỉ dùng khi model tương ứng hỗ trợ rõ ràng
callback_urlstringKhôngChỉ dùng khi model đích hỗ trợ callback rõ ràng; tích hợp mới mặc định không gửi
metadataobjectKhôngCác trường mở rộng như video tham chiếu, âm thanh tham chiếu, khung hình đầu và cuối

Cách yêu cầu độ phân giải

Endpoint tạo là thống nhất, nhưng cách chọn độ phân giải của từng model có thể khác nhau. Trước tiên hãy dựa vào ID model do GET /v1/models trả về và mô tả khả năng của model.

Model độ phân giải cố định: chọn độ phân giải qua ID model

Một số model đã cố định mức đầu ra trong tên hoặc ID model. Khách hàng nên lấy từ GET /v1/models các ID mà tài khoản hiện tại thực sự dùng được, rồi chọn trực tiếp model có mức tương ứng:

{
  "model": "<FIXED_RESOLUTION_MODEL_ID>",
  "prompt": "Một chiếc xe chậm rãi chạy qua con phố trong cảnh đêm thành phố",
  "duration": 8,
  "ratio": "16:9"
}

Với model độ phân giải cố định, resolution không thể tạm thời thay đổi mức đầu ra. Kể cả khi client gửi kèm resolution, cũng đừng coi đó là cách ghi đè khả năng của model.

Model chọn được độ phân giải: dùng trường resolution

Một số model có thể hỗ trợ nhiều độ phân giải; khi đó hãy truyền resolution viết thường trong yêu cầu:

{
  "model": "<MULTI_RESOLUTION_MODEL_ID>",
  "prompt": "Một chiếc xe chậm rãi chạy qua con phố trong cảnh đêm thành phố",
  "duration": 8,
  "resolution": "720p",
  "ratio": "16:9"
}

Độ phân giải và giá trị thực sự dùng được vẫn theo /v1/models của API Key hiện tại, mô tả model và kiểm tra phía máy chủ. Nếu client cần tương thích với nhiều model, có thể giữ trường resolution: model hỗ trợ trường này dùng nó để chọn mức đầu ra, còn model mức cố định lấy model làm chuẩn. Không dùng quality, vquality hoặc tự sửa tên model để lách giới hạn khả năng của model; tổ hợp không được hỗ trợ sẽ trả về 400.

4. Ảnh, video và âm thanh tham chiếu

3.1 Ảnh tham chiếu

Nhiều ảnh thống nhất dùng mảng images ở cấp gốc:

{
  "model": "<MODEL_ID>",
  "prompt": "Giữ nhất quán khuôn mặt, trang phục và tông màu của nhân vật, chậm rãi bước đi bên bờ biển, ánh sáng dịu chất điện ảnh",
  "duration": 10,
  "ratio": "16:9",
  "images": [
    "https://cdn.example.com/character-front.jpg",
    "https://cdn.example.com/character-side.jpg"
  ]
}

Một ảnh cũng có thể dùng image hoặc input_reference, nhưng tích hợp mới nên thống nhất dùng mảng images.

3.2 Video tham chiếu

Với các model hỗ trợ nhiều video tham chiếu trong bảng khả năng, đặt vào metadata.referenceVideos:

{
  "model": "<MODEL_ID>",
  "prompt": "Tham khảo nhịp cắt cảnh và chuyển động máy quay, nhưng giữ nguyên ngoại hình chủ thể",
  "duration": 8,
  "ratio": "16:9",
  "images": ["https://cdn.example.com/subject.jpg"],
  "metadata": {
    "referenceVideos": [
      "https://cdn.example.com/camera-motion.mp4"
    ]
  }
}

3.3 Âm thanh tham chiếu

Với các model hỗ trợ âm thanh tham chiếu trong bảng khả năng, đặt vào metadata.referenceAudios:

{
  "model": "<MODEL_ID>",
  "prompt": "Nhân vật hát theo nhịp của âm thanh tham chiếu, khẩu hình tự nhiên",
  "duration": 10,
  "ratio": "16:9",
  "images": ["https://cdn.example.com/singer.jpg"],
  "metadata": {
    "referenceAudios": [
      "https://cdn.example.com/voice-reference.mp3"
    ]
  }
}

Với ảnh tham chiếu của MiniMax H3, đừng dùng images ở cấp gốc mà hãy dùng metadata.referenceImages, xem Quy chuẩn MiniMax Video API.

3.4 Tư liệu hỗn hợp cho model đa tham chiếu

{
  "model": "<MODEL_ID>",
  "prompt": "Dùng ngoại hình sản phẩm từ ảnh tham chiếu, nhịp máy quay từ video tham chiếu và không khí âm thanh từ âm thanh tham chiếu để làm quảng cáo công nghệ",
  "duration": 12,
  "ratio": "16:9",
  "images": [
    "https://cdn.example.com/product-front.jpg",
    "https://cdn.example.com/product-detail.jpg"
  ],
  "metadata": {
    "referenceVideos": [
      "https://cdn.example.com/motion-reference.mp4"
    ],
    "referenceAudios": [
      "https://cdn.example.com/sound-reference.mp3"
    ]
  }
}

Không dùng đồng thời nhiều trường bí danh cho cùng một loại tư liệu. Ví dụ, không gửi cùng lúc images ở cấp gốc và metadata.referenceImages; khi có nhiều trường tương thích cùng tồn tại, gateway sẽ chọn một nhóm và không bảo đảm gộp lại.

3.5 Yêu cầu đối với URL tư liệu

  1. Video và âm thanh tham chiếu dùng URL HTTPS công khai, tải xuống được ẩn danh. blob:, đường dẫn cục bộ và data:video/... không phải giao thức video tham chiếu chung; tệp cục bộ hãy tải lên trước theo mục tiếp theo.
  2. URL không được phụ thuộc vào Cookie, trạng thái đăng nhập trình duyệt, Referer hay header tùy chỉnh.
  3. Không dùng localhost, IP mạng riêng, tên dịch vụ Docker hoặc địa chỉ chỉ truy cập được trong mạng nội bộ của bạn.
  4. URL phải còn hiệu lực trong suốt quá trình tạo video; khuyến nghị thời hạn hiệu lực không dưới 60 phút.
  5. Ảnh nên dùng JPG, PNG hoặc WebP; video nên dùng MP4/MOV; âm thanh nên dùng MP3/WAV.
  6. Video nên không quá 50 MB mỗi đoạn, âm thanh không quá 15 MB mỗi đoạn; tư liệu quá lớn có thể thất bại ở bước tải xuống hoặc kiểm duyệt.
  7. Trước khi tham chiếu tư liệu của bên thứ ba, hãy bảo đảm bạn có đủ quyền tác giả, quyền hình ảnh và quyền sử dụng cần thiết.

Trang tư liệu mở được không có nghĩa là tải được byte video: địa chỉ đích phải trả về tệp media thực, không phải trang chia sẻ HTML. URL dùng được cũng không có nghĩa là mã hóa, thời lượng hay kích thước đáp ứng yêu cầu của model đích. Ảnh cho model video Google còn có thể dùng Base64 hoặc tải tệp lên, xem Quy chuẩn Google Video API; video tham chiếu vẫn chỉ nhận URL.

3.6 Tải lên tư liệu cục bộ: xin cấp quyền trước, rồi PUT

Endpoint này cung cấp kho lưu trữ media tạm thời; nó không tạo video, không chuyển mã, không nén và không tự động trích xuất khung hình.

Bước 1: xin quyền tải lên ở phía máy chủ.

POST https://api.88api.ai/v1/media/uploads
Authorization: Bearer sk-xxxx
Content-Type: application/json
{
  "size": 1234567,
  "mime_type": "video/mp4",
  "sha256": "<SHA-256 của byte gốc của tệp, mã hóa Base64URL không padding>"
}
TrườngQuy tắc
sizeSố byte của tệp gốc, số nguyên từ 1 đến 100,000,000; không phải độ dài chuỗi Base64
mime_typePhải khớp với định dạng thật của tệp và Content-Type khi tải lên
sha256Bản tóm tắt SHA-256 32 byte mã hóa Base64URL, không có =; không phải chuỗi thập lục phân 64 ký tự

Hỗ trợ tải lên: video/mp4, video/webm, video/quicktime; audio/mpeg, audio/wav, audio/ogg, audio/mp4, audio/flac; image/png, image/jpeg, image/webp, image/gif. Giới hạn tải lên không phải giới hạn tham chiếu của model; tư liệu vẫn phải đáp ứng giới hạn chặt hơn của model đích.

Minh họa phản hồi HTTP 200 (phần trong dấu ngoặc nhọn là giá trị giữ chỗ, không phải giá trị thật để gọi):

{
  "url": "https://assets.88api.ai/media/reference-media/<object-id>.mp4",
  "upload_url": "https://assets.88api.ai/media/reference-media/<object-id>.mp4",
  "method": "PUT",
  "headers": {
    "Content-Type": "video/mp4",
    "X-Media-Upload-Token": "<scoped-upload-token>"
  },
  "expires_at": 1789373400
}

expires_at ở đây là thời điểm hết hạn của quyền tải lên (giây Unix, khoảng 10 phút), không phải thời hạn lưu tệp; lúc này url cũng chưa có nghĩa là tệp đã tải lên xong. Quyền này chỉ cho phép ghi vào tệp được chỉ định, không truy cập được toàn bộ bucket.

Bước 2: tải dữ liệu nhị phân gốc lên trước khi quyền hết hạn. Dùng nguyên upload_url, method và headers trong phản hồi:

# Các biến sau lấy từ phản hồi xin quyền; không gửi API_KEY tới địa chỉ tải lên.
curl --request PUT "$UPLOAD_URL" \
  --header 'Content-Type: video/mp4' \
  --header "X-Media-Upload-Token: $UPLOAD_TOKEN" \
  --data-binary @reference.mp4

Đây không phải multipart/form-data, không dùng -F, cũng không phải Base64 trong JSON. Phải gửi Content-Length, MIME và byte tệp khớp với lúc xin quyền; trình duyệt có thể gửi trực tiếp File/Blob và tự tạo Content-Length. Client phía máy chủ dạng luồng cũng phải cung cấp đúng độ dài, không dùng tải lên chunked với độ dài không xác định.

HTTP 201 nghĩa là tải lên đã hoàn tất, trả về url, size, mime_type, expires_at. expires_at lần này mới là thời hạn của tư liệu (giây Unix, 30 ngày kể từ khi đối tượng được tải lên). Sau khi xác nhận thành công, đặt url vào trường tham chiếu mà model đích yêu cầu. Trình duyệt nên xin quyền qua backend của chính bạn rồi tải lên trực tiếp, không được để lộ API Key dài hạn ở frontend.

Không cho phép ghi đè cùng một địa chỉ tải lên: PUT lặp lại trả về 409, hãy dùng lại địa chỉ đã tải lên thành công. Khi gặp sự cố mạng không rõ kết quả, trước tiên dùng HEAD ẩn danh để kiểm tra URL đó, đừng lập tức xin đối tượng mới. Việc tải lên và tham chiếu không tạo tác vụ tạo video mới.

5. Chế độ khung hình đầu và cuối cho model hỗ trợ

Khung hình đầu và khung hình cuối đặt trong metadata.firstFrame và metadata.lastFrame:

Ví dụ dưới đây áp dụng cho model đa tham chiếu hỗ trợ các trường này. Veo và Gemini Omni cũng nhận metadata.firstFrame và metadata.lastFrame; các cách viết khác xem Quy chuẩn Google Video API.

{
  "model": "<MODEL_ID>",
  "prompt": "Chuyển mượt từ quảng trường vắng lúc sáng sớm sang cùng bố cục vào ban đêm khi đèn bật sáng",
  "duration": 10,
  "ratio": "16:9",
  "metadata": {
    "firstFrame": "https://cdn.example.com/start.jpg",
    "lastFrame": "https://cdn.example.com/end.jpg"
  }
}

Quy tắc:

  • lastFrame không thể dùng riêng, phải đồng thời cung cấp firstFrame.
  • Phần lớn model hỗ trợ khung hình đầu và cuối không cho phép dùng chung khung hình đầu/cuối với ảnh/video/âm thanh tham chiếu thông thường.
  • Khi bảng khả năng của model ghi “loại trừ lẫn nhau”, hãy chọn một trong hai: khung hình đầu và cuối hoặc tư liệu tham chiếu thông thường.

6. Bảng khả năng của các model video hiện tại

Các bảng sau mô tả khả năng công khai của model đã được tổng hợp, không phải ID model cố định khi khách hàng gửi yêu cầu. Yêu cầu thực tế phải dùng id mà API Key hiện tại lấy từ /v1/models. Các tài khoản khác nhau có thể thấy ID model khác nhau; chỉ cần dùng giao thức /v1/videos thống nhất, client không cần biết máy chủ chọn model như thế nào.

Quy chuẩn theo từng thương hiệu

Cách viết tham số và trường tư liệu của các thương hiệu không hoàn toàn giống nhau; trước khi tích hợp, hãy đọc trang của thương hiệu tương ứng:

Trang thương hiệuModel
Seedance bản chính thứcseedance-2.5官方版, seedance-2.0官方版, seedance-2.0-fast官方版, seedance-2.0-mini官方版
SD bản đám mâySD2.5, SD2.0
Seedance chuyển tiếp chính thứcSeedance 2.0 Mini (đa độ phân giải), Seedance 2.0 (đa độ phân giải), Seedance 2.0 Fast (đa độ phân giải), Seedance 2.5 (đa độ phân giải)
Googleveo-3.1, veo-3.1-fast, gemini-omni-flash, gemini-omni-flash-1.1
Klingkling-3.0-turbo
MiniMaxminimax-h3 (giống minimax_h3)
Wanwan3.0-video, wan3.0-video-prime
Grokgrok-imagine-video, grok-imagine-video-1.5

Bảng tổng hợp khả năng model

STTKhả năng / tên công khaiThời lượngTỷ lệ khung hìnhĐầu raGiới hạn ảnh / video / âm thanhKhung hình đầu và cuốiĐiều khiển âm thanh
1seedance-2.5官方版4–30 giây, mặc định 4 giây16:9 9:16 1:1 4:3 3:4 21:9720P30 / 10 / 10Có, loại trừ với tư liệu thông thườngKhông thiết lập được
2seedance-2.0官方版4–15 giây, mặc định 4 giâyNhư trên720P9 / 3 / 3Có, loại trừ với tư liệu thông thườngKhông thiết lập được
3seedance-2.0-fast官方版4–15 giây, mặc định 4 giâyNhư trên720P9 / 3 / 3Có, loại trừ với tư liệu thông thườngKhông thiết lập được
4seedance-2.0-mini官方版4–15 giây, mặc định 5 giâyNhư trên480P/720P, mặc định 480P9 / 3 / 3Mô tả model không liệt kê chế độ khung hình đầu và cuốiKhông có tùy chọn tạo âm thanh
5SD2.54–30 giâyBắt buộc chỉ định: 16:9 9:16 1:1 4:3 3:4 21:9480P/720P/1080P, mặc định 480P30 / 10 / 10Có, loại trừ với tư liệu thông thườngHỗ trợ generate_audio
6SD2.04–15 giây1:1 (mặc định) 21:9 16:9 9:16 3:4 4:3480P/720P/1080P/4K, mặc định 480P9 / 3 / 3, tổng ba loại tối đa 12Có, loại trừ với tư liệu thông thườngKhông thiết lập được
7Seedance 2.0 Mini (đa độ phân giải)4–15 giây; hỗ trợ -1, mặc định 5 giâyadaptive (mặc định) 16:9 4:3 1:1 3:4 9:16 21:9480P/720P9 / 3 / 3Có, loại trừ với tư liệu thông thườngMặc định có âm thanh, có thể tắt
8Seedance 2.0 (đa độ phân giải)4–15 giây; hỗ trợ -1, mặc định 5 giâyNhư trên480P/720P/1080P/4K9 / 3 / 3Có, loại trừ với tư liệu thông thườngMặc định có âm thanh, có thể tắt
9Seedance 2.0 Fast (đa độ phân giải)4–15 giây; hỗ trợ -1, mặc định 5 giâyNhư trên480P/720P9 / 3 / 3Có, loại trừ với tư liệu thông thườngMặc định có âm thanh, có thể tắt
10Seedance 2.5 (đa độ phân giải)4–30 giây; hỗ trợ -1, mặc định -1 (tự động)Như trên480P/720P/1080P30 / 10 / 10Có, loại trừ với tư liệu thông thườngMặc định có âm thanh, có thể tắt
11veo-3.1-fast4/6/8 giây; dùng ảnh tham chiếu cố định 8 giây; mỗi lần kéo dài thêm 7 giây16:9 9:16720P/1080P/4K3 / 0 / 0Hỗ trợ khung hình đầu, khung hình đầu và cuối, ảnh tham chiếuÂm thanh gốc, có thể tắt bằng generate_audio
12veo-3.14/6/8 giây; dùng ảnh tham chiếu cố định 8 giây; mỗi lần kéo dài thêm 7 giây16:9 9:16720P/1080P/4K3 / 0 / 0Hỗ trợ khung hình đầu, khung hình đầu và cuối, ảnh tham chiếuÂm thanh gốc, có thể tắt bằng generate_audio
13gemini-omni-flash3–10 giây16:9 9:16720P10 / 1 / 0Hỗ trợ khung hình đầu, khung hình đầu và cuốiKhông hỗ trợ
14gemini-omni-flash-1.13–10 giây, mỗi lần kéo dài thêm khoảng 10 giây16:9 9:16720P/1080P/4K10 / 3 / 0Hỗ trợ khung hình đầu, khung hình đầu và cuốiKhông hỗ trợ
15kling-3.0-turbo4–15 giây16:9 9:16 1:1720P/1080P/2K/4K, mặc định 720P30 (gồm khung hình đầu/cuối) / 10 / 0Có, dùng được cùng ảnh tham chiếuHỗ trợ generate_audio
16minimax-h34–15 giây, mặc định 5 giây16:9 (mặc định) 9:16 1:1 4:3 3:4 21:9768P/1080P/2K (siêu phân giải), mặc định 768P9 / – / 3Có, loại trừ với tư liệu thông thườngVideo luôn có track âm thanh, không thiết lập được
17wan3.0-video2–30 giây, mặc định 5 giây; hỗ trợ -1adaptive (mặc định) 16:9 9:16 1:1 4:3 3:4 21:9480P/720P/1080P, mặc định 1080P10 / 5 / 5 (dùng mảng media)Có, loại trừ với tư liệu thông thườngKhông thiết lập được
18wan3.0-video-primeNhư trênNhư trênNhư trênNhư trênNhư trênKhông thiết lập được
19grok-imagine-video1–15 giây, mặc định 8 giây16:9 9:16 1:1 4:3 3:4 3:2 2:3 (dùng aspect_ratio)480P/720P1 / 0 / 0Không hỗ trợKhông hỗ trợ
20grok-imagine-video-1.51–15 giây, mặc định 8 giâyNhư trên480P/720P/1080P1 / 0 / 0Không hỗ trợKhông hỗ trợ

Số thứ tự trong bảng bắt đầu từ 1, chỉ để tiện đọc, không phải tham số yêu cầu. Tên công khai và dòng khả năng trong bảng cũng chỉ để đối chiếu; khi gọi thực tế phải dùng id chính xác mà /v1/models trả về cho API Key hiện tại, không tự ghép tên hay suy đoán quan hệ ánh xạ.

Quy tắc riêng của từng model

Trường của các thương hiệu khác nhau khá nhiều; dưới đây là những điểm dễ sai nhất, quy tắc đầy đủ xem các trang thương hiệu ở trên:

  • Wan wan3.0-video*: khung hình đầu, khung hình cuối và tư liệu tham chiếu nên gửi trong mảng media. metadata.firstFrame/lastFrame/referenceImages/referenceVideos/referenceAudios (kể cả dạng gạch dưới), firstFrame/first_frame/lastFrame/last_frame ở cấp gốc và images ở cấp gốc (ảnh 1 là khung hình đầu, ảnh 2 là khung hình cuối, tối đa 2) cũng được chuyển thành media. Trường tư liệu không đọc được sẽ trả lỗi 400.
  • Với Grok, aspect_ratio và ratio tương đương nhau; khi điền nhiều trường tỷ lệ cùng lúc, các giá trị phải giống nhau. Tối đa 1 ảnh đầu vào.
  • Ảnh tham chiếu của minimax-h3 dùng metadata.referenceImages, khung hình đầu/cuối dùng metadata.firstFrame/lastFrame; ý nghĩa của images ở cấp gốc thay đổi tùy tuyến được phân bổ, đừng dựa vào nó. 480P và 4K đã ngừng cung cấp.
  • Video tham chiếu của Veo và Gemini Omni dùng video/videos ở cấp gốc, không dùng metadata.referenceVideos.
  • Seedance chuyển tiếp chính thức cũng nhận các trường số ít image, input_reference, video, audio làm tư liệu tham chiếu, cùng một tư liệu điền lặp lại chỉ được tính một lần; size mâu thuẫn với ratio, aspect_ratio hoặc resolution sẽ trả về 400. Nhóm mô hình này còn hỗ trợ thư viện chân dung ảo, bản nháp 2.5, khung hình cuối, tìm kiếm web, hủy tác vụ đang xếp hàng và ID tác vụ chính thức; xem Seedance chuyển tiếp chính thức.
  • SD2.5 bắt buộc chỉ định rõ tỷ lệ khung hình, không hỗ trợ auto.
  • Seedance bản chính thức, SD2.5, SD2.0 và kling-3.0-turbo mỗi dòng chỉ có một tên model; dùng resolution để chọn mức độ phân giải và tính phí theo mức đó. Nếu không truyền, dùng mức thấp nhất của dòng model; mức không được hỗ trợ trả về 400. Các tên cũ có hậu tố mức độ phân giải (ví dụ SD2.5 720P, SD2.0 4K) vẫn gọi được nhưng cố định theo mức trong tên, và không còn hiển thị trong quảng trường model. Các tên Kling cũ có hậu tố mức độ phân giải đã ngừng cung cấp.

7. Phản hồi khi gửi

Sau khi gửi thành công, ID tác vụ công khai được trả về ngay:

{
  "id": "task_xxxxxxxxxxxxxxxxxxxxxxxx",
  "object": "video",
  "model": "<MODEL_ID>",
  "status": "queued",
  "progress": 0,
  "created_at": 1788249600
}

Hãy lưu id. Endpoint tiêu chuẩn không bảo đảm trả về task_id; tích hợp mới bắt buộc dùng id.

HTTP 200 nghĩa là “tác vụ đã được tiếp nhận”, không có nghĩa là video đã tạo xong.

8. Truy vấn định kỳ tác vụ

curl 'https://api.88api.ai/v1/videos/task_xxxxxxxxxxxxxxxxxxxxxxxx' \
  --header 'Authorization: Bearer sk-xxxx'

Trạng thái thống nhất

statusÝ nghĩaHành động của client
queuedĐã tiếp nhận, đang xếp hàngTiếp tục chờ
in_progressĐang tạoTiếp tục chờ
completedĐã hoàn tấtĐọc url được trả về để phát, tải xuống hoặc tham chiếu lại
failedĐã thất bạiĐọc error.code và error.message
unknownTrạng thái không xác địnhThử lại sau giây lát; nếu kéo dài, liên hệ hỗ trợ

Ví dụ phản hồi hoàn tất:

{
  "id": "task_xxxxxxxxxxxxxxxxxxxxxxxx",
  "object": "video",
  "model": "<MODEL_ID>",
  "status": "completed",
  "progress": 100,
  "created_at": 1788249600,
  "completed_at": 1788250200,
  "url": "https://assets.88api.ai/media/task-videos/2026/09/<object-key>.mp4",
  "video_url": "https://assets.88api.ai/media/task-videos/2026/09/<object-key>.mp4",
  "result_url": "https://assets.88api.ai/media/task-videos/2026/09/<object-key>.mp4"
}

Ví dụ phản hồi thất bại:

{
  "id": "task_xxxxxxxxxxxxxxxxxxxxxxxx",
  "object": "video",
  "model": "<MODEL_ID>",
  "status": "failed",
  "progress": 100,
  "error": {
    "code": "video_generation_failed",
    "message": "Tạo video thất bại"
  }
}

Chiến lược truy vấn định kỳ

  1. Sau khi gửi, chờ 3–5 giây rồi mới truy vấn lần đầu.
  2. Khuyến nghị truy vấn mỗi 10–15 giây một lần, không truy vấn mỗi giây.
  3. Client tương tác có thể dùng ngân sách chờ 15 phút; phía máy chủ có thể lưu ID tác vụ để tiếp tục truy vấn hoặc nhận callback. Client ngừng chờ không có nghĩa là tác vụ thất bại, bị hủy hay được hoàn tiền.
  4. Trong lúc queued hoặc in_progress, không gửi lại cùng tác vụ, nếu không có thể bị tính phí trùng.
  5. Khi đồng thời cấu hình callback, truy vấn định kỳ có thể làm cơ chế dự phòng.

Thời gian chờ của một yêu cầu HTTP, tổng thời gian chờ của client và thời hạn tác vụ phía máy chủ là các khái niệm khác nhau. Đừng coi 15 phút là bảo đảm máy chủ chắc chắn kết thúc tác vụ, cũng đừng chỉ dựa vào progress=100 để kết luận thành công; hãy lấy status làm chuẩn. Sau khi khôi phục trang hoặc mạng, có thể tiếp tục truy vấn tác vụ ban đầu.

9. Callback (Webhook)

Mục này chỉ áp dụng cho model hỗ trợ callback rõ ràng. Model video Google (Veo, Gemini Omni) và các model khác chưa hỗ trợ callback rõ ràng hãy dùng truy vấn định kỳ, không đặt callback_url.

Thiết lập khi gửi

{
  "model": "<MODEL_ID>",
  "prompt": "Flycam quay thành phố cyberpunk",
  "duration": 8,
  "size": "16:9",
  "callback_url": "https://api.customer.example/webhooks/88api/video"
}

callback_url phải:

  • là URL HTTP(S) truy cập được từ Internet; môi trường production rất nên dùng HTTPS;
  • không quá 2048 ký tự;
  • không trỏ tới IP mạng riêng, localhost, địa chỉ link-local hoặc cổng bị hạn chế;
  • trả về bất kỳ phản hồi 2xx nào trong vòng 15 giây.

Callback chỉ được gửi ở trạng thái cuối:

  • video.completed
  • video.failed

Header của callback

Content-Type: application/json
User-Agent: NewAPI-Task-Callback/1.0
X-NewAPI-Event: video.completed
X-NewAPI-Delivery: evt_task_xxx_completed
X-NewAPI-Timestamp: 1788250200
X-NewAPI-Signature: sha256=<hex-hmac>

Sự kiện hoàn tất

{
  "id": "evt_task_xxxxxxxxxxxxxxxxxxxxxxxx_completed",
  "type": "video.completed",
  "created_at": 1788250200,
  "data": {
    "task_id": "task_xxxxxxxxxxxxxxxxxxxxxxxx",
    "model": "<MODEL_ID>",
    "status": "completed",
    "progress": "100%",
    "output_url": "https://assets.88api.ai/media/task-videos/2026/09/<object-key>.mp4",
    "created_at": 1788249600,
    "completed_at": 1788250200
  }
}

Sự kiện thất bại

{
  "id": "evt_task_xxxxxxxxxxxxxxxxxxxxxxxx_failed",
  "type": "video.failed",
  "created_at": 1788250200,
  "data": {
    "task_id": "task_xxxxxxxxxxxxxxxxxxxxxxxx",
    "model": "<MODEL_ID>",
    "status": "failed",
    "progress": "100%",
    "error": "Tạo video thất bại",
    "created_at": 1788249600,
    "completed_at": 1788250200
  }
}

Xác minh chữ ký callback

Khóa ký là toàn bộ API Key đã dùng khi gửi tác vụ đó (gồm cả tiền tố sk-). Chuỗi gốc được ký là:

X-NewAPI-Timestamp + "." + byte gốc của thân yêu cầu

Ví dụ xác minh bằng Node.js:

import crypto from "node:crypto";
import express from "express";

const app = express();

// Phải giữ lại thân yêu cầu gốc trước, rồi mới phân tích JSON.
app.use(
  express.json({
    verify: (req, _res, buffer) => {
      req.rawBody = buffer;
    },
  }),
);

app.post("/webhooks/88api/video", (req, res) => {
  const timestamp = req.header("X-NewAPI-Timestamp") || "";
  const timestampNumber = Number(timestamp);
  if (
    !Number.isFinite(timestampNumber) ||
    Math.abs(Date.now() / 1000 - timestampNumber) > 300
  ) {
    return res.status(401).send("stale timestamp");
  }
  const received = (req.header("X-NewAPI-Signature") || "").replace(
    /^sha256=/,
    "",
  );
  const secret = process.env.API_KEY; // toàn bộ sk-xxxx
  const signed = Buffer.concat([
    Buffer.from(`${timestamp}.`, "utf8"),
    req.rawBody,
  ]);
  const expected = crypto
    .createHmac("sha256", secret)
    .update(signed)
    .digest("hex");

  const valid =
    received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  if (!valid) return res.status(401).send("invalid signature");

  // Khử trùng lặp lũy đẳng bằng X-NewAPI-Delivery hoặc req.body.id.
  console.log(req.body.type, req.body.data.task_id);
  return res.sendStatus(204);
});

Thử lại callback và tính lũy đẳng

  • Khi callback hết thời gian chờ, lỗi mạng hoặc trả về mã không phải 2xx, 88API sẽ thử lại với độ trễ tăng theo cấp số nhân.
  • Thời gian chờ mỗi yêu cầu là 15 giây, tối đa 8 lần thử, độ trễ tối đa giữa các lần là 15 phút.
  • Cùng một sự kiện có thể được gửi nhiều lần. Client bắt buộc dùng X-NewAPI-Delivery hoặc id của sự kiện để khử trùng lặp lũy đẳng.
  • Khuyến nghị từ chối callback lệch hơn 5 phút so với giờ của máy chủ bạn, để giảm rủi ro phát lại.
  • Khuyến nghị xác minh chữ ký, lưu sự kiện rồi trả về 2xx ngay; logic nghiệp vụ tốn thời gian hãy đưa vào hàng đợi bất đồng bộ của riêng bạn.

10. Lấy tệp video

Sau khi tác vụ chuyển sang completed, ưu tiên dùng url trong kết quả truy vấn; client tương thích có thể đọc dự phòng video_url / result_url, còn callback dùng data.output_url. Các trường này là kết quả do máy chủ giao, không tự dựng đường dẫn media từ ID tác vụ hay ID nội bộ.

# RESULT_URL lấy nguyên từ phản hồi hoàn tất; không gửi API Key tới máy chủ media.
curl --fail --location "$RESULT_URL" --output result.mp4

Lưu ý:

  • Client phải cho phép chuyển hướng HTTP 3xx; curl cần kèm --location.
  • Kết quả được lưu trữ trên trang dùng địa chỉ ẩn danh https://assets.88api.ai/media/...; kết quả chính thức đáp ứng điều kiện liên kết trực tiếp an toàn giữ nguyên URL gốc, không bắt buộc đổi sang tên miền của trang.
  • Các sản phẩm khác không thể công khai trực tiếp hoặc không thể ánh xạ rõ ràng có thể tiếp tục trả về địa chỉ tương thích kèm tham số truy cập. Hãy giữ nguyên toàn bộ URL và các tham số cần thiết mà máy chủ trả về, không tự xóa tham số hay thay tên miền; không thêm tham số truy vấn vào địa chỉ assets (sẽ trả về 400).
  • /v1/videos/{id}/content cũ cung cấp chuyển hướng tương thích 307 cho video đã lưu trữ, không nên dùng làm liên kết trực tiếp chính thức hay lối vào bắt buộc cho mọi sản phẩm; khi trả về 404, hãy truy vấn lại tác vụ để lấy địa chỉ kết quả thực tế, đừng tạo lại một cách mù quáng.
  • output_url trong callback là địa chỉ kết quả không trong suốt, có thể có thời hạn; không phân tích tên miền hay cấu trúc đường dẫn của nó.
  • Không ghép, lưu hoặc phụ thuộc vào bất kỳ ID tác vụ nào khác; chỉ dùng id công khai trong phản hồi khi gửi.
  • Nếu cần lưu thành phẩm lâu dài, hãy tải về kho lưu trữ đối tượng của riêng bạn sau khi hoàn tất.

9.1 Lưu trữ, phát và tham chiếu giữa các model

  • Tư liệu tải lên trang và video lưu trữ được giữ 30 ngày kể từ khi mỗi đối tượng được tải lên; đọc lại, truy vấn tác vụ cũ hay tham chiếu lại đều không gia hạn, cũng không tải lên bản mới. URL kết quả chính thức theo thời hạn hiệu lực của nguồn, không cam kết 30 ngày.
  • Không có tham số chữ ký và không cần đăng nhập không có nghĩa là lưu vĩnh viễn; tệp hết hạn trả về 410, sau khi bị dọn theo vòng đời có thể trả về 404. Bản ghi tác vụ còn tồn tại không có nghĩa là tệp nhị phân vẫn còn.
  • Tư liệu công khai hỗ trợ GET, HEAD, Range một đoạn và đọc chéo nguồn từ trình duyệt. Có thể đọc X-Media-Expires-At (giây Unix); Range thành công thường trả về 206. Bộ nhớ đệm không kéo dài thời hạn ban đầu, cũng không bảo đảm mọi khu vực có cùng tốc độ tải xuống.
  • Kết quả do Veo tạo có thể dùng làm đầu vào cho các model khác hỗ trợ video tham chiếu, với điều kiện địa chỉ còn hiệu lực và định dạng, kích thước, thời lượng đáp ứng yêu cầu của model đích. Đặt trực tiếp URL kết quả vào trường tham chiếu của model đó, không cần tải xuống rồi tải lên lại; bản thân Veo chỉ nhận video đầu vào khi kéo dài video, không hỗ trợ video tham chiếu.
{
  "model": "<MODEL_ID>",
  "prompt": "Giữ nguyên chủ thể và chuyển động của video tham chiếu, tiếp nối cảnh quay",
  "duration": 4,
  "ratio": "16:9",
  "metadata": {
    "referenceVideos": ["<URL HTTPS đầy đủ của kết quả do tác vụ trước trả về>"]
  }
}

11. Ví dụ JavaScript đầy đủ về truy vấn định kỳ

Node.js 22+; ở phía máy chủ, đặt API_KEY, MODEL_ID và REFERENCE_FILE (đường dẫn tệp MP4). MODEL_ID phải lấy từ phản hồi GET /v1/models của API Key hiện tại. Đặt TASK_ID để tiếp tục trực tiếp tác vụ cũ mà không tải lên hay tạo lại. Ví dụ sẽ tạo một lần tạo video có tính phí, cuối cùng chỉ in thân yêu cầu cho lượt tiếp theo, không tự động tạo lần thứ hai. Để dễ đọc, ví dụ dùng bộ đệm tệp; khi xử lý hàng loạt tệp lớn, hãy giới hạn số lượng đồng thời.

// video-api-example.mjs — Node.js 22+, run with API_KEY, MODEL_ID, and REFERENCE_FILE.
import { readFile, stat } from "node:fs/promises";
import { createHash } from "node:crypto";

const apiKey = process.env.API_KEY;
if (!apiKey) throw new Error("Set API_KEY on your server");
const baseUrl = "https://api.88api.ai";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function apiJSON(path, body) {
  // No automatic POST retry: a timed-out create may already have been accepted.
  const response = await fetch(baseUrl + path, {
    method: "POST",
    headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
    body: JSON.stringify(body),
    signal: AbortSignal.timeout(120000),
  });
  if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  return response.json();
}

async function uploadMedia(file, mimeType) {
  const info = await stat(file);
  if (!info.isFile() || info.size < 1 || info.size > 100000000) {
    throw new Error("File must contain 1–100000000 bytes");
  }
  const bytes = await readFile(file);
  const permit = await apiJSON("/v1/media/uploads", {
    size: bytes.length, mime_type: mimeType,
    sha256: createHash("sha256").update(bytes).digest("base64url"),
  });
  const target = new URL(permit.upload_url);
  if (target.origin !== "https://assets.88api.ai" || permit.method !== "PUT") {
    throw new Error("Unexpected upload destination; do not send credentials");
  }
  const response = await fetch(target, {
    method: "PUT", headers: permit.headers, body: bytes,
    redirect: "error", signal: AbortSignal.timeout(120000),
  });
  // A 409/ambiguous upload is not automatically retried; HEAD permit.url first.
  if (response.status !== 201) throw new Error(`Upload HTTP ${response.status}`);
  return response.json();
}

async function waitForVideo(id, budgetMs = 15 * 60 * 1000) {
  const deadline = Date.now() + budgetMs;
  let delay = 4000;
  while (Date.now() < deadline) {
    await sleep(Math.min(delay, deadline - Date.now()));
    const remaining = deadline - Date.now();
    if (remaining <= 0) break;
    let response, state;
    try {
      response = await fetch(`${baseUrl}/v1/videos/${encodeURIComponent(id)}`, {
        headers: { Authorization: `Bearer ${apiKey}` },
        signal: AbortSignal.timeout(Math.min(30000, remaining)),
      });
      if (response.ok) state = await response.json();
    } catch {
      delay = Math.min(delay * 2, 60000);
      continue; // Retry a read, never create a replacement task.
    }
    if (response.status === 429 || response.status >= 500) {
      const retryAfter = response.headers.get("Retry-After");
      const seconds = retryAfter && /^\d+$/.test(retryAfter)
        ? Number(retryAfter)
        : (Date.parse(retryAfter || "") - Date.now()) / 1000;
      delay = Number.isFinite(seconds) && seconds > 0
        ? Math.max(1000, seconds * 1000)
        : Math.min(delay * 2, 60000);
      await response.body?.cancel();
      continue;
    }
    if (!response.ok) throw new Error(`Task ${id}: HTTP ${response.status}`);
    if (state.status === "completed") {
      const result = state.url || state.video_url || state.result_url;
      if (typeof result !== "string" || new URL(result).protocol !== "https:") {
        throw new Error(`Task ${id} completed without a usable HTTPS result URL`);
      }
      return result; // Keep official URLs and capability query parameters intact.
    }
    if (state.status === "failed") {
      throw new Error(`Task ${id}: ${state.error?.message || "video generation failed"}`);
    }
    if (!["queued", "in_progress", "unknown"].includes(state.status)) {
      throw new Error(`Task ${id}: unexpected status ${state.status}`);
    }
    delay = 12000;
  }
  throw new Error(`Stopped waiting for ${id}; save this ID and query later. Not cancelled.`);
}

async function main() {
  // Resume a saved task without uploading or creating another paid task.
  let id = process.env.TASK_ID;
  if (!id) {
    if (!process.env.MODEL_ID) throw new Error("Set MODEL_ID from GET /v1/models");
    if (!process.env.REFERENCE_FILE) throw new Error("Set REFERENCE_FILE");
    const media = await uploadMedia(process.env.REFERENCE_FILE, "video/mp4");
    const task = await apiJSON("/v1/videos", {
      model: process.env.MODEL_ID, prompt: "Giữ nguyên chủ thể và chuyển động của video tham chiếu, tiếp nối cảnh quay",
      duration: 4, ratio: "16:9", metadata: { referenceVideos: [media.url] },
    });
    id = task.id;
    if (!id) throw new Error("Create response did not contain a task ID; do not blindly retry");
    console.log("Save task ID:", id); // Persist to your database in production.
  }
  const resultURL = await waitForVideo(id);
  console.log("Result URL:", resultURL); // This grants media access; do not log publicly.
  // This only shows the NEXT request body; it does not create a second paid task.
  console.log(JSON.stringify({
    model: process.env.MODEL_ID, prompt: "Giữ nguyên chủ thể và chuyển động của video tham chiếu, tiếp nối cảnh quay",
    duration: 4, ratio: "16:9", metadata: { referenceVideos: [resultURL] },
  }, null, 2));
}

await main();

12. Xử lý lỗi và thử lại

Tình huốngKhuyến nghị xử lý
HTTP 400Kiểm tra tên model, thời lượng, tỷ lệ khung hình, số lượng tư liệu và vị trí trường
HTTP 401API Key bị thiếu, sai hoặc đã mất hiệu lực
HTTP 403Key hiện tại không có quyền với model hoặc nhóm
HTTP 404Tác vụ không tồn tại, không thuộc tài khoản hiện tại, hoặc thành phẩm chưa sẵn sàng
Tải lên HTTP 409Đối tượng chỉ định đã tồn tại và không thể ghi đè; dùng HEAD xác nhận trước rồi dùng lại địa chỉ đã thành công
Media HTTP 410Tệp đã hết hạn; đọc và tham chiếu lại không gia hạn, sau khi bị dọn cũng có thể là 404
Tải lên HTTP 415Chữ ký tệp không khớp với MIME đã khai báo; không thể chỉ đổi phần mở rộng để giả làm định dạng khác
Media HTTP 416Range vượt ngoài phạm vi tệp hoặc định dạng không hỗ trợ; dùng HEAD lại để xác nhận kích thước
HTTP 429Gửi yêu cầu quá nhanh hoặc bị giới hạn hạn mức; theo Retry-After hoặc độ trễ tăng theo cấp số nhân
HTTP 5xxLỗi dịch vụ tạm thời; yêu cầu truy vấn có thể thử lại với độ trễ
status=failedTruy vấn HTTP thành công nhưng tạo video thất bại; đọc error.message
Tải tư liệu thất bạiXác nhận URL truy cập được từ Internet, chưa hết hạn, không có chống hotlink hay phụ thuộc xác thực
unsupported_reference_mediaModel không hỗ trợ loại tham chiếu này; tải lên hay đổi tên miền đều không giải quyết được
must be a public HTTPS URLTệp cục bộ hãy tải lên trước; không thể gửi Blob, Data URL, HTTP hoặc địa chỉ cần đăng nhập
input_download_failedKiểm tra khả năng truy cập thực tế của dịch vụ đích, DNS/mạng và thời hạn của media, không chỉ xem máy của bạn có mở được hay không
Bị chặn bởi kiểm duyệt nội dungĐiều chỉnh prompt hoặc tư liệu tham chiếu, không thử lại nguyên trạng vô hạn

POST /v1/videos hiện chưa cung cấp bảo đảm lũy đẳng theo khóa do khách hàng tự định nghĩa. Nếu khách hàng gặp hết thời gian chờ mạng sau khi đã gửi yêu cầu đầy đủ, đừng gửi lại ngay một cách mù quáng, nếu không có thể tạo và bị tính phí nhiều tác vụ.