Tích hợp API tạo video: task, polling và content
Tích hợp tạo video bằng create/poll/content flow, nhận biết edits và extensions, webhook, duration bound và authorized media delivery.
Tích hợp API tạo video là một asynchronous media workflow: bạn tạo job, chờ tới khi hoàn thành hoặc thất bại, rồi download byte qua content URL có thể hết hạn và phải được authorize. Trong OpenAI-compatible gateway theo Videos surface, các route cốt lõi là POST /v1/videos, GET /v1/videos/{task_id} và GET /v1/videos/{task_id}/content. Tài liệu OpenAI chính thức hiện đặt edits, extensions, characters, webhooks và Batch cạnh create/retrieve, trong khi endpoint remix cũ đã deprecated cho công việc mới. Bài viết này chỉ cách tích hợp các flow đó an toàn bằng behavior nhìn thấy trong repository và official documentation, không bịa latency hoặc price.
Bối cảnh ngày 2026-07-15: OpenAI đã công bố deprecation Videos API và alias model Sora 2, shutdown vào 2026-09-24 (OpenAI deprecations). Đừng bắt đầu product dài hạn mới chỉ dựa trên Sora 2. Nếu vẫn vận hành Sora traffic qua gateway, hãy coi đây là migration window: giữ create/poll/content hoạt động, tránh xây UX mới quanh remix-only flow và lập kế hoạch dual-vendor alias ngay.
Trả lời trực tiếp: coi video là task, không phải chat completion
# 1) create
curl -sS https://YOUR_GATEWAY/v1/videos \
-H "Authorization: Bearer $GATEWAY_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "YOUR_VIDEO_MODEL_ALIAS",
"prompt": "Slow aerial push-in over a misty cedar forest at dawn",
"seconds": "4",
"size": "1280x720"
}'
# 2) poll (prefer webhooks when the upstream supports them; see below)
curl -sS https://YOUR_GATEWAY/v1/videos/TASK_ID \
-H "Authorization: Bearer $GATEWAY_KEY"
# 3) download when status=completed
curl -sS https://YOUR_GATEWAY/v1/videos/TASK_ID/content \
-H "Authorization: Bearer $GATEWAY_KEY" \
-o forest.mp4
Các status cần xử lý tường minh, theo video DTO của codebase này, gồm queued, in_progress, completed và failed. Field thường có trên task object gồm id, model, status, progress, created_at, completed_at, expires_at, seconds, size, optional error và lineage field như remixed_from_video_id khi có derivative job.
Luôn kiểm tra lại parameter matrix trong tài liệu hiện tại của vendor—video generation guide và Videos API reference của OpenAI. Vendor khác như Google Veo / Gemini video surface, Runway, Luma, Kling, ByteDance/Volcengine có first-party reference dùng job shape khác. Gateway cũng có thể expose route riêng vendor như /v1/video/generations hoặc Kling-style /kling/v1/videos/* tùy channel adapter. Ưu tiên normalized OpenAI-style task API trong application code khi gateway hỗ trợ.
Surface OpenAI chính thức và route gateway đăng ký hôm nay
| Capability, OpenAI docs 2026-07-15 | Official route / event | Thực tế gateway trong repository này |
|---|---|---|
| Create video | POST /v1/videos | Được hỗ trợ như task submit path chính cho OpenAI-style adapter |
| Retrieve / poll | GET /v1/videos/{id} | Được hỗ trợ |
| Download content | GET /v1/videos/{id}/content | Được hỗ trợ với ownership check |
| Edit video hiện có | POST /v1/videos/edits (ưu tiên; remix deprecated) | Hiện chưa normalize thành route set hạng nhất của gateway |
| Extend video | POST /v1/videos/extensions | Hiện chưa normalize |
| Characters | POST /v1/videos/characters + characters trên create | Hiện chưa normalize |
| Legacy remix | POST /v1/videos/{id}/remix (deprecated cho integration mới) | Vẫn được detect/register trên remix-style path cho Sora-style adapter hiện có |
| Webhooks | video.completed, video.failed | Feature upstream OpenAI; gateway client vẫn nên có poll fallback |
| Batch | Dòng Batch API tới POST /v1/videos | Feature upstream; không thay thế product job table |
Nêu rõ gap này trong architecture review: product code chỉ biết create/poll/content sẽ sống sót khi route đa vendor; product code hard-depend vào remix như nút “edit” sẽ phải chống cả OpenAI deprecation và việc gateway chưa normalize đầy đủ edits/extensions/characters.
Vì sao tích hợp video khó hơn ảnh
| Concern | Images, thường gặp | Videos, thường gặp |
|---|---|---|
| Latency | vài giây | hàng chục giây tới vài phút |
| API shape | thường một response | create + wait + content |
| Payload size | KB–MB | dễ lên nhiều MB |
| Failure mode | moderation / bad size | timeout dài, partial progress, content hết hạn |
| Cost risk | n và resolution | duration, resolution, edit/extension chain |
| AuthZ sau create | tùy chọn | bắt buộc khi download content |
Nếu áp chat-completion timeout, ví dụ hard cancel sau 60s mà không polling, video integration sẽ trông “hỏng” dù job thành công sau đó. Thiết kế job table trong application từ ngày đầu.
Data model application được khuyên dùng
video_jobs
id (your id)
provider_task_id
model
prompt_hash / prompt_text (policy-dependent)
status
progress
seconds
size
billing_owner_type / billing_owner_id
team_id / project_id (nullable)
content_expires_at
storage_key (nullable until persisted)
source_task_id (edit/extension/remix lineage)
last_error
created_at / updated_at
Worker update status và progress từ poll hoặc webhook. Khi completed, download worker fetch /content một lần, lưu vào object storage và set storage_key. Khi failed, surface error.message / error.code mà không retry vô hạn.
Bound duration và billing
Duration là billing multiplier. Repository này cap video duration do user cung cấp bằng MaxTaskDurationSeconds = 3600 và reject seconds ngoài range trong validation. Product nên đặt UX maximum chặt hơn, ví dụ 4–12 giây cho preview, dù platform cho dài hơn. Example create chính thức của OpenAI thường dùng bucket giây rời rạc, gồm variant 16/20 giây dài hơn trong tài liệu Sora hiện tại; đừng bịa duration tự do.
Quy tắc integration:
- parse
secondscẩn thận; DTO có thể giữ dưới dạng string - không tin duration do client cung cấp để hiển thị price nếu server chưa re-validate
- pre-consume quota từ billing owner, user hoặc team, không chỉ actor
- settle hoặc refund khi task fail sau pre-consume
- áp cùng discipline cho edit/extension/remix chain để user không vô tình khuếch đại cost
Xem kiến trúc LLM gateway đa tenant cho tách owner và actor, cùng LLM usage log và cost tracking cho field attribution.
Chiến lược chờ: webhook trước, polling làm fallback
Video guide của OpenAI khuyên polling khoảng mỗi 10–20 giây với backoff, và ưu tiên webhook (video.completed / video.failed) khi có. Download URL được document là ngắn hạn, khoảng 1 giờ sau generation trên hướng dẫn OpenAI, nên copy vào storage sớm.
Default thực dụng cho product worker:
- Register webhook khi provider và gateway/ops path hỗ trợ signed delivery.
- Poll làm safety net: bắt đầu gần 10s, thêm jitter, backoff tới 20–30s dưới load hoặc khi lặp
in_progress. - Honor
Retry-Afterkhi có. - Cap tổng thời gian chờ bằng hard timeout và terminal local state
expired/failed. - Không poll mỗi vài trăm millisecond cho từng browser tab.
type VideoTask = {
id: string;
status: "queued" | "in_progress" | "completed" | "failed" | string;
progress?: number;
expires_at?: number;
error?: { message?: string; code?: string };
};
async function sleep(ms: number) {
await new Promise((r) => setTimeout(r, ms));
}
async function pollVideo(taskId: string, timeoutMs = 15 * 60_000): Promise<VideoTask> {
const started = Date.now();
let interval = 10_000;
while (Date.now() - started < timeoutMs) {
const res = await fetch(`${process.env.GATEWAY_BASE}/v1/videos/${taskId}`, {
headers: { Authorization: `Bearer ${process.env.GATEWAY_KEY}` },
});
if (res.status === 429) {
const ra = Number(res.headers.get("retry-after"));
await sleep(Number.isFinite(ra) ? ra * 1000 : interval);
interval = Math.min(interval + 5_000, 30_000);
continue;
}
if (!res.ok) throw new Error(await res.text());
const task = (await res.json()) as VideoTask;
if (task.status === "completed" || task.status === "failed") return task;
const jitter = Math.floor(Math.random() * 2_000);
await sleep(interval + jitter);
interval = Math.min(Math.floor(interval * 1.2), 30_000);
}
throw new Error("video poll timeout");
}
Tune từ completion time đã đo cho model và region của bạn, không theo blog team khác.
AuthZ khi download content và scope workbench
Tạo task không đồng nghĩa được phép stream byte sau đó. Trong gateway đa tenant, /v1/videos/{id}/content phải enforce ownership: cùng user/team/project scope đã tạo job hoặc explicit share policy. Đoán task id cross-tenant phải fail closed.
Workbench path trong repository này:
- UI: video workbench
- personal API dưới
/api/user/video-workbench - team API dưới
/api/teams/:team_id/video-workbenchvới action theo project scope - grant riêng như generate video và manage video asset
Điều này khác image workbench chỉ personal. Nếu object storage bắt buộc cho durable workbench playback, hãy configure trước khi hứa permanent history. Fallback play URL trỏ tới /v1/videos/{id}/content vẫn cần live authZ và có thể chịu expires_at.
Edits, extensions, characters và legacy remix
Hướng dẫn OpenAI chính thức cho integration mới:
- Edits:
POST /v1/videos/editsvới prompt và sourcevideođể thay đổi có mục tiêu; remix đã deprecated cho công việc này. - Extensions:
POST /v1/videos/extensionsđể tiếp tục clip completed; tổng duration được stitch, và character không được hỗ trợ trên extension theo guide OpenAI. - Characters:
POST /v1/videos/characters, rồi reference character ID trên create để giữ consistency. - Batch: offline queue các JSONL line
POST /v1/videos; batch output có retention window riêng trong tài liệu OpenAI.
Tip integration vẫn áp dụng cho mọi derivative job, gồm legacy remix còn trong gateway này:
- chỉ cho derivative job khi caller đọc được source task
- copy tenancy field từ source; đừng bỏ derivative dưới sai project
- cho user thấy edit/extension/remix là job billable mới, không phải chỉnh miễn phí
- persist lineage cho support và moderation
- feature-flag metadata bag riêng vendor; validate duration/size bằng global bound
Cho tới khi gateway normalize edits/extensions/characters, giữ call đó sau explicit adapter flag hoặc native upstream client; đừng giả remix là modern OpenAI path.
Policy persistence
Hãy giả định:
- upstream content URL và cả gateway temporary proxy đều có thể hết hạn (
expires_atlà field hạng nhất; OpenAI document khoảng 1 giờ cho interactive download) - product page cần object key của bạn
- thumbnail được derive sau download, không scrape ad hoc từ provider CDN
- phải xóa được khi team rời đi hoặc user thực thi retention right
Pipeline an toàn:
completed → authorized content fetch → virus/type sniff nếu cần → object storage put → DB storage_key → serve qua CDN của bạn với app auth.
Bỏ storage “tới lúc cần” là cách demo biến thành incident ticket, đặc biệt đau nếu upstream Videos API cũng đang sunset.
Observability và support
Log ít nhất gateway request id / task id, model alias, seconds, size, status transition, billing owner và actor, webhook hay poll path, cùng final error code. Dashboard nên tách failure class video khỏi chat. Support tooling nên mở task và usage log mà không download full MP4 vào ticket system.
Route thay thế có thể gặp
Tùy channel adapter, operator cũng có thể enable:
- endpoint kiểu
/v1/video/generations - Kling
/kling/v1/videos/* - Gemini/Vertex Veo task adapter
- vendor task API khác sau Jimeng hoặc route tương tự
Application team không nên hard-code mọi vendor path. Ưu tiên một normalized client (/v1/videos + stable alias) và để gateway map channel. Khi feature chỉ có trên vendor path như motion brush hoặc cinema preset, cô lập sau feature flag và document contract không portable.
Đọc liên quan
Nguồn
- OpenAI video generation guide
- OpenAI Videos API reference
- OpenAI deprecations, Videos API / Sora 2 → 2026-09-24
- OpenAI webhooks guide
- Google Gemini video generation
- Runway developer documentation
- Luma AI API documentation
- OWASP Top 10 for LLM Applications
Hỏi đáp vận hành
Tôi có thể block HTTP request cho tới khi video xong không?
Chỉ với internal tool ngắn. Product UI nên tạo job record và dùng webhook hoặc polling. Long synchronous request khuếch đại timeout gateway và load balancer.
Nên dùng polling interval nào?
Mặc định gần 10 giây với jitter, backoff tới 20–30 giây, honor Retry-After và ưu tiên webhook video.completed / video.failed khi có. Guide OpenAI nêu khoảng polling 10–20 giây.
Vì sao download content thất bại sau status completed?
Nguyên nhân thường gặp: content hết hạn, sai auth scope, task thuộc project khác hoặc storage/proxy cấu hình sai. Kiểm expires_at và tenancy field trước khi retry generation.
Product mới có nên dùng remix không?
Không làm edit path chính. Official OpenAI docs hướng công việc edit mới tới POST /v1/videos/edits; remix đã deprecated. Gateway này có thể vẫn nhận remix-style route để compatibility—hãy coi đó là legacy bridge, không phải greenfield design.
Tôi có cần video workbench nếu đã gọi /v1/videos không?
Không cho pure backend pipeline. Có khi human cần shared history, preset hoặc quản lý asset theo team scope trong UI.
Worker đầu tiên cần ship
Triển khai một worker tạo video ngắn qua stable non-Sora alias nếu có, hoặc alias Sora rõ ràng là tạm thời với migration date viết trước 2026-09-24. Poll bằng 10s+jitter, webhook tùy chọn, download /content một lần, upload object storage và chứng minh key của team thứ hai không thể download task id. Chỉ sau đó mới nối happy path vào product UI.