Menambahkan pembuatan video ke aplikasi Anda tidak sama dengan menambahkan pembuatan gambar. Panggilan API mengembalikan respons segera — tetapi videonya belum siap. Anda mendapatkan ID tugas, dan Anda harus terus bertanya “apakah sudah selesai?” sampai benar-benar selesai.
Kebanyakan developer mengalami ini saat pertama kali memanggil API video, menunggu body respons berisi URL video, tetapi justru mendapatkan ID tugas. Panduan ini menjelaskan alur lengkapnya: mengirim tugas, melakukan polling hasil, menangani kegagalan, dan menyimpan output sebelum URL kedaluwarsa.
Apa yang akan Anda buat
Layanan backend yang menerima prompt teks atau gambar, mengirim tugas pembuatan video, melakukan polling hingga selesai, dan mengembalikan URL video final. Anda akan bekerja dengan empat model — Veo 3 Fast, Sora 2, Kling Video, dan Runway — semuanya menggunakan satu kunci API.
Prasyarat:
- Python 3.8+ atau Node.js 18+
- Kunci CometAPI
- Pemahaman dasar tentang REST API
Pahami mengapa pembuatan video berbeda
Pada pembuatan gambar, Anda mengirim permintaan dan mendapatkan gambar dalam respons yang sama. Pembuatan video menggunakan antrean tugas async:
- Submit permintaan pembuatan → mendapatkan
task_id - Poll endpoint status setiap beberapa detik
- Saat status mencapai keadaan terminal, Anda mendapatkan URL video
- Unduh dan simpan video — URL bersifat sementara
Jika Anda memperlakukan pembuatan video seperti pembuatan gambar dan menunggu respons pertama berisi video, permintaan Anda akan selalu time out.
Dalam layanan web produksi, loop polling ini sebaiknya dijalankan di worker latar belakang (Celery, Bull, atau sejenisnya), bukan di request handler Anda. Contoh di bawah menggunakan polling sinkron — cocok untuk skrip dan prototipe, tetapi tidak untuk menangani pengguna bersamaan.
Pilih model
| Model | Penyedia | Durasi maksimum | Harga (via CometAPI) | Terbaik untuk |
|---|---|---|---|---|
| Veo 3 Fast | 8 detik | $0.05/detik | Prototyping cepat, klip media sosial | |
| Sora 2 | OpenAI (via CometAPI model ID) | ~10 detik | $0.08/detik | Video pendek kreatif berkualitas tinggi |
| Kling Video | Kuaishou | 10 detik | $0.13–$2.64/tugas | Konten pemasaran, kontrol granular |
| Runway Gen-3A Turbo | Runway | 5 atau 10 detik | $0.32/tugas | Gambar-ke-video, konten komersial |
Source*: Halaman model CometAPI, Mei 2026. Catatan: "Sora 2" adalah* identifier model CometAPI — lihat halaman model mereka untuk detail model yang mendasari.
- Veo 3 Fast mendukung text-to-video dan image-to-video. Termurah per detik, titik awal yang baik.
- Sora 2 menghasilkan audio secara native bersama video — dialog, suara ambient, dan efek tanpa langkah TTS terpisah.
- Kling Video memberi Anda
negative_prompt,cfg_scale, pengaturan pergerakan kamera, dan modepro. Kontrol paling granular di antara keempatnya. - Runway hanya image-to-video melalui CometAPI. Berikan gambar statis dan deskripsi gerak, lalu sistem akan menganimasikannya.
Kirim tugas Veo
Veo menggunakan multipart/form-data. Gunakan files= di Python requests untuk mengirim dengan benar — data=dict mengirim application/x-www-form-urlencoded, yang bukan hal yang sama:
import requestsimport osfrom dotenv import load_dotenvload_dotenv()def submit_veo_task(prompt: str, size: str = "16x9") -> str: """Mengirim tugas Veo 3 Fast text-to-video. Mengembalikan task_id.""" api_key = os.getenv("COMETAPI_KEY") if not api_key: raise ValueError("Variabel lingkungan COMETAPI_KEY tidak disetel") response = requests.post( "https://api.cometapi.com/v1/videos", headers={"Authorization": f"Bearer {api_key}"}, files={ "prompt": (None, prompt), "model": (None, "veo3-fast"), "size": (None, size) }, timeout=30 ) response.raise_for_status() return response.json()["id"]task_id = submit_veo_task("A paper kite drifting above a wheat field on a windy afternoon")print(f"Tugas dikirim: {task_id}")
Lakukan polling untuk hasilnya
import timedef poll_veo_task(task_id: str, interval: int = 10, max_wait: int = 600) -> str: """Polling hingga tugas Veo selesai. Mengembalikan URL video.""" api_key = os.getenv("COMETAPI_KEY") if not api_key: raise ValueError("Variabel lingkungan COMETAPI_KEY tidak disetel") headers = {"Authorization": f"Bearer {api_key}"} url = f"https://api.cometapi.com/v1/videos/{task_id}" elapsed = 0 while elapsed < max_wait: response = requests.get(url, headers=headers, timeout=30) response.raise_for_status() result = response.json() status = result.get("status") if status == "succeeded": return result["output"][0] elif status in ("failed", "cancelled"): raise RuntimeError( f"Tugas {task_id} gagal dengan status '{status}': " f"{result.get('error', 'tidak ada detail error yang dikembalikan')}" ) time.sleep(interval) elapsed += interval raise TimeoutError(f"Tugas {task_id} tidak selesai dalam {max_wait} detik")video_url = poll_veo_task(task_id)print(f"Video siap: {video_url}")
Gunakan Kling Video untuk kontrol lebih lanjut
Kling memiliki struktur endpoint berbeda dan menggunakan JSON. Perhatikan bahwa string status terminal Kling adalah "succeed" (bukan "succeeded") — ini sesuai format respons API:
def submit_kling_task(prompt: str, duration: str = "5", mode: str = "std") -> str: """Mengirim tugas Kling text-to-video. Mengembalikan task_id.""" api_key = os.getenv("COMETAPI_KEY") if not api_key: raise ValueError("Variabel lingkungan COMETAPI_KEY tidak disetel") response = requests.post( "https://api.cometapi.com/kling/v1/videos/text2video", headers={ "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" }, json={ "model_name": "kling-v1-6", "prompt": prompt, "negative_prompt": "blurry, low quality, watermark", "cfg_scale": 0.5, "mode": mode, # "std" atau "pro" "aspect_ratio": "16:9", "duration": duration # "5" atau "10" }, timeout=30 ) response.raise_for_status() return response.json()["data"]["task_id"]def poll_kling_task(task_id: str, interval: int = 10, max_wait: int = 600) -> str: """Polling tugas Kling hingga selesai. Mengembalikan URL video.""" api_key = os.getenv("COMETAPI_KEY") if not api_key: raise ValueError("Variabel lingkungan COMETAPI_KEY tidak disetel") headers = {"Authorization": f"Bearer {api_key}"} url = f"https://api.cometapi.com/kling/v1/videos/text2video/{task_id}" elapsed = 0 while elapsed < max_wait: response = requests.get(url, headers=headers, timeout=30) response.raise_for_status() result = response.json() status = result["data"]["task_status"] if status == "succeed": # Kling menggunakan "succeed", bukan "succeeded" return result["data"]["task_result"]["videos"][0]["url"] elif status == "failed": error_detail = result.get("data", {}).get("task_result", "no detail") raise RuntimeError( f"Tugas Kling {task_id} gagal: {error_detail}" ) time.sleep(interval) elapsed += interval raise TimeoutError(f"Tugas Kling {task_id} time out setelah {max_wait}s")
Source*:* Dokumentasi CometAPI Kling Video
Animasi gambar statis dengan Runway
Runway hanya mendukung image-to-video. Ini juga memerlukan header tambahan (X-Runway-Version):
def submit_runway_task(image_url: str, motion_prompt: str, duration: int = 5) -> str: """Mengirim tugas Runway image-to-video. Mengembalikan task_id.""" api_key = os.getenv("COMETAPI_KEY") if not api_key: raise ValueError("Variabel lingkungan COMETAPI_KEY tidak disetel") response = requests.post( "https://api.cometapi.com/runwayml/v1/image_to_video", headers={ "Authorization": f"Bearer {api_key}", "X-Runway-Version": "2024-11-06", "Content-Type": "application/json" }, json={ "model": "gen3a_turbo", "promptImage": image_url, # harus URL HTTPS yang stabil "promptText": motion_prompt, "duration": duration, "ratio": "1280:720", "watermark": False }, timeout=30 ) response.raise_for_status() return response.json()["id"]def poll_runway_task(task_id: str, interval: int = 5, max_wait: int = 600) -> str: """Polling tugas Runway. Mengembalikan URL video saat selesai.""" api_key = os.getenv("COMETAPI_KEY") if not api_key: raise ValueError("Variabel lingkungan COMETAPI_KEY tidak disetel") headers = { "Authorization": f"Bearer {api_key}", "X-Runway-Version": "2024-11-06" } url = f"https://api.cometapi.com/runwayml/v1/tasks/{task_id}" elapsed = 0 while elapsed < max_wait: response = requests.get(url, headers=headers, timeout=30) response.raise_for_status() result = response.json() status = result.get("status") if status == "task_not_exist": # Khusus CometAPI: tugas masih inisialisasi, coba lagi setelah beberapa detik time.sleep(interval) elapsed += interval continue elif status == "succeeded": return result["output"][0] elif status in ("failed", "cancelled"): raise RuntimeError(f"Tugas Runway {task_id} gagal: {result.get('error', 'no detail')}") time.sleep(interval) elapsed += interval raise TimeoutError(f"Tugas Runway {task_id} time out setelah {max_wait}s")
Source*:* Dokumentasi CometAPI Runway
Simpan video sebelum URL kedaluwarsa
URL video dari API pembuatan bersifat sementara. Unduh file segera dan simpan di tempat yang Anda kendalikan:
import requestsimport pathlibdef download_video(url: str, output_path: str) -> None: """Mengunduh video dari URL ke file lokal menggunakan streaming.""" out = pathlib.Path(output_path) if out.parent != pathlib.Path("."): out.parent.mkdir(parents=True, exist_ok=True) with requests.get(url, stream=True, timeout=60) as r: r.raise_for_status() with open(out, "wb") as f: for chunk in r.iter_content(chunk_size=8192): f.write(chunk) print(f"Tersimpan ke {output_path}")# Alur lengkaptask_id = submit_veo_task("A timelapse of clouds moving over a city skyline")video_url = poll_veo_task(task_id)download_video(video_url, "output/city_timelapse.mp4")
Di produksi, ganti penulisan file lokal dengan unggahan ke S3, Cloudflare R2, atau penyimpanan pilihan Anda. Pola streaming tetap sama — alirkan byte secara langsung alih-alih memuat seluruh video ke memori.
Tangani kegagalan
| Gejala | Kemungkinan penyebab | Solusi |
|---|---|---|
| Tugas macet di queued selama 10+ menit | Beban server atau model tidak tersedia | Coba lagi dengan model berbeda |
| task_not_exist pada polling Runway pertama | Tugas masih inisialisasi | Tunggu 5 detik dan coba lagi — perilaku CometAPI yang didokumentasikan |
| failed tanpa pesan error | Prompt memicu content filter | Ubah redaksi prompt |
| URL video mengembalikan 403 | URL kedaluwarsa sebelum diunduh | Unduh segera setelah mendapatkan URL |
| Time out setelah 10 menit | Pembuatan memakan waktu terlalu lama | Tingkatkan max_wait atau beralih ke Veo 3 Fast |
| Kling mengembalikan "succeed" bukan "succeeded" | API Kling menggunakan string status non-standar | Ini benar — lihat kode polling Kling di atas |
Source: Dokumentasi pembuatan video CometAPI
Versi Node.js
Node.js 18+ menyertakan fetch dan FormData secara native. Contoh ini mencakup keempat model:
// Node.js 18+ — tidak perlu paket tambahanconst API_KEY = process.env.COMETAPI_KEY;if (!API_KEY) throw new Error('COMETAPI_KEY tidak disetel');// --- Veo 3 Fast ---async function submitVeoTask(prompt, size = '16x9') { const form = new FormData(); form.append('prompt', prompt); form.append('model', 'veo3-fast'); form.append('size', size); const res = await fetch('https://api.cometapi.com/v1/videos', { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}` }, body: form }); if (!res.ok) throw new Error(`Veo submit gagal: ${res.status}`); return (await res.json()).id;}async function pollVeoTask(taskId, intervalMs = 10000, maxWaitMs = 600000) { let elapsed = 0; while (elapsed < maxWaitMs) { const res = await fetch(`https://api.cometapi.com/v1/videos/${taskId}`, { headers: { 'Authorization': `Bearer ${API_KEY}` } }); if (!res.ok) throw new Error(`Polling gagal: ${res.status}`); const result = await res.json(); if (result.status === 'succeeded') return result.output[0]; if (['failed', 'cancelled'].includes(result.status)) { throw new Error(`Tugas ${taskId} gagal: ${result.error ?? 'no detail'}`); } await new Promise(r => setTimeout(r, intervalMs)); elapsed += intervalMs; } throw new Error(`Tugas ${taskId} time out`);}// --- Kling Video ---async function submitKlingTask(prompt, duration = '5', mode = 'std') { const res = await fetch('https://api.cometapi.com/kling/v1/videos/text2video', { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ model_name: 'kling-v1-6', prompt, negative_prompt: 'blurry, low quality, watermark', cfg_scale: 0.5, mode, aspect_ratio: '16:9', duration }) }); if (!res.ok) throw new Error(`Kling submit gagal: ${res.status}`); return (await res.json()).data.task_id;}async function pollKlingTask(taskId, intervalMs = 10000, maxWaitMs = 600000) { let elapsed = 0; while (elapsed < maxWaitMs) { const res = await fetch( `https://api.cometapi.com/kling/v1/videos/text2video/${taskId}`, { headers: { 'Authorization': `Bearer ${API_KEY}` } } ); if (!res.ok) throw new Error(`Kling polling gagal: ${res.status}`); const result = await res.json(); const status = result.data.task_status; if (status === 'succeed') return result.data.task_result.videos[0].url; if (status === 'failed') { throw new Error(`Tugas Kling ${taskId} gagal: ${JSON.stringify(result.data.task_result ?? 'no detail')}`); } await new Promise(r => setTimeout(r, intervalMs)); elapsed += intervalMs; } throw new Error(`Tugas Kling ${taskId} time out`);}// --- Runway (image-to-video) ---async function submitRunwayTask(imageUrl, motionPrompt, duration = 5) { const res = await fetch('https://api.cometapi.com/runwayml/v1/image_to_video', { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'X-Runway-Version': '2024-11-06', 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'gen3a_turbo', promptImage: imageUrl, promptText: motionPrompt, duration, ratio: '1280:720', watermark: false }) }); if (!res.ok) throw new Error(`Runway submit gagal: ${res.status}`); return (await res.json()).id;}async function pollRunwayTask(taskId, intervalMs = 5000, maxWaitMs = 600000) { let elapsed = 0; while (elapsed < maxWaitMs) { const res = await fetch( `https://api.cometapi.com/runwayml/v1/tasks/${taskId}`, { headers: { 'Authorization': `Bearer ${API_KEY}`, 'X-Runway-Version': '2024-11-06' } } ); if (!res.ok) throw new Error(`Runway polling gagal: ${res.status}`); const result = await res.json(); const status = result.status; if (status === 'task_not_exist') { // Khusus CometAPI: tugas masih inisialisasi await new Promise(r => setTimeout(r, intervalMs)); elapsed += intervalMs; continue; } if (status === 'succeeded') return result.output[0]; if (['failed', 'cancelled'].includes(status)) { throw new Error(`Tugas Runway ${taskId} gagal: ${result.error ?? 'no detail'}`); } await new Promise(r => setTimeout(r, intervalMs)); elapsed += intervalMs; } throw new Error(`Tugas Runway ${taskId} time out`);}// Contoh penggunaanconst taskId = await submitVeoTask('A paper kite drifting above a wheat field');const videoUrl = await pollVeoTask(taskId);console.log('Video siap:', videoUrl);
Langkah berikutnya
Anda kini memiliki kode yang berfungsi untuk empat model video, loop polling yang menangani kegagalan, dan langkah pengunduhan yang mencegah Anda kehilangan konten yang dihasilkan.
Masalah berikut yang paling sering ditemui developer: mereka menghardcode satu model, dan beralih ke opsi yang lebih murah atau lebih cepat berarti harus menyentuh banyak file. Artikel berikut membahas cara merutekan permintaan lintas model tanpa menulis ulang kode Anda.
Berikutnya: Cara Beralih Antar Model AI Tanpa Menulis Ulang Kode Anda
FAQ
T: Mengapa saya mendapatkan ID tugas alih-alih video dalam respons API?
Pembuatan video bersifat async — model seperti Veo, Sora, Kling, dan Runway membutuhkan 2–5 menit untuk merender. API segera mengembalikan ID tugas agar permintaan Anda tidak time out. Anda melakukan polling ke endpoint status terpisah hingga tugas mencapai status terminal (succeeded, succeed, failed).
T: Berapa lama URL video yang dihasilkan tetap valid?
URL video dari API pembuatan bersifat sementara. Unduh file segera setelah mendapatkan URL dan simpan di penyimpanan Anda sendiri (S3, Cloudflare R2, dll.). Jangan menyimpan URL dan berharap masih berfungsi beberapa jam kemudian.
T: Apa perbedaan antara Veo 3 Fast dan Kling Video?
Veo 3 Fast lebih murah ($0.05/detik), lebih cepat, dan lebih sederhana dipanggil. Kling Video memberi Anda kontrol lebih: negative_prompt, cfg_scale, pengaturan pergerakan kamera, dan mode kualitas pro. Jika Anda perlu menyetel hasil dengan lebih presisi, gunakan Kling. Jika Anda memerlukan kecepatan dan biaya rendah, gunakan Veo 3 Fast.
T: Bisakah saya membuat video dari gambar alih-alih prompt teks?
Ya. Veo mendukung image-to-video dengan meneruskan file input_reference. Kling mendukung melalui endpoint /kling/v1/videos/image2video dengan parameter image (URL atau base64). Runway hanya image-to-video — tidak menerima prompt teks saja melalui CometAPI.
T: Mengapa Runway mengembalikan task_not_exist saat polling pertama?
Ini adalah perilaku CometAPI yang didokumentasikan — tugas masih dalam proses inisialisasi di backend. Tunggu beberapa detik dan coba lagi. Ini bukan error. Kode polling di atas menanganinya secara otomatis.
T: Mengapa Kling menggunakan "succeed" alih-alih "succeeded"?
Itu adalah format respons API Kling yang sebenarnya. Bukan typo. Veo dan Runway menggunakan "succeeded" — Kling menggunakan "succeed". Jika Anda membuat wrapper polling terpadu, Anda perlu menangani kedua string tersebut.
T: Apakah loop polling sinkron aman digunakan dalam web server?
Tidak. Loop polling dalam panduan ini memblokir thread selama beberapa menit. Dalam layanan web nyata dengan pengguna bersamaan, jalankan polling di worker latar belakang (Celery untuk Python, Bull untuk Node.js). Kirim tugas di request handler, kembalikan ID tugas ke klien, dan biarkan worker memberi tahu klien saat video siap.
