Video Generation
LLM API provides text-to-video, image-to-video, reference-to-video, and video editing through a unified, asynchronous API compatible with the OpenAI Sora interface. Requests are routed to the selected model's provider — Veo, Kling, Sora, HappyHorse, Runway, WAN, Seedance, Grok, Vidu, Pixverse, LTX, HunYuan, and others — all via a single endpoint.
How It Works
Video generation is asynchronous. Unlike chat or audio, a video job takes 30–180 seconds to complete. The flow is:
POST /v1/videos — submit a job, receive an id immediately
GET /v1/videos/{id} — poll for status until completed or failed
GET /v1/videos/{id}/content — stream the MP4 file through the gateway
Alternatively, provide a webhook URL in the request and get notified automatically on completion.
Quick Start
Submit a Job
curl -X POST "https://api.llmapi.ai/v1/videos" \
-H "Authorization: Bearer $LLM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kling-v2.1-master-t2v",
"prompt": "A majestic eagle soars over snow-capped mountains at golden hour",
"seconds": 5
}'
Response 202 Accepted:
{
"id": "abc123",
"object": "video",
"status": "queued",
"progress": 0,
"model": "kling-v2.1-master-t2v",
"seconds": "5",
"size": "1920x1080",
"usage": { "duration_seconds": 5 },
"created_at": 1700000000
}
Poll for Status
curl "https://api.llmapi.ai/v1/videos/abc123" \
-H "Authorization: Bearer $LLM_API_KEY"
Response when complete:
{
"id": "abc123",
"object": "video",
"status": "completed",
"progress": 100,
"model": "kling-v2.1-master-t2v",
"seconds": "5",
"size": "1920x1080",
"usage": { "duration_seconds": 5 },
"provider_job_id": "prov_xyz",
"video_url": "https://cdn.example.com/abc123.mp4",
"format": "mp4",
"duration_seconds": 5,
"width": 1920,
"height": 1080,
"cost": 1.08,
"created_at": 1700000000,
"completed_at": 1700000182
}
The response is OpenAI Sora-compatible: object: "video", seconds is a string, and usage.duration_seconds is always populated when duration is known.
Download the Video
curl "https://api.llmapi.ai/v1/videos/abc123/content" \
-H "Authorization: Bearer $LLM_API_KEY" \
--output video.mp4
The /content endpoint streams the MP4 directly from the provider CDN through the gateway — no need to handle provider URLs on your side.
Webhook Notifications
Instead of polling, provide a webhook URL in the request body. The gateway will POST the completed job status to your URL as soon as generation finishes:
curl -X POST "https://api.llmapi.ai/v1/videos" \
-H "Authorization: Bearer $LLM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "veo3.1-fast-t2v",
"prompt": "A time-lapse of a sunflower blooming",
"seconds": 8,
"webhook": "https://your-app.com/hooks/video-done"
}'
Your endpoint will receive a POST with the full VideoGenerationStatusResponse body identical to GET /v1/videos/{id}.
Image-to-Video
Models with the -i2v suffix accept image input alongside the prompt.
Single Image (image_url)
Most I2V models use the image as the first frame:
{
"model": "kling-v2.1-master-i2v",
"prompt": "The cat slowly turns its head and blinks",
"image_url": "https://example.com/cat.jpg",
"seconds": 5
}
Multiple Images (image_list)
Some models (Grok I2V, Vidu v2 I2V, Pixverse v6 I2V) accept an array of images:
{
"model": "grok-i2v",
"prompt": "Animate this scene",
"image_list": ["https://example.com/frame.jpg"],
"seconds": 5
}
Start-End Frame Transition
Models like vidu-q2-pro-start-end-video and pixverse-v6-transition generate a video that morphs from the first image to the second. Pass exactly two images in image_list:
{
"model": "vidu-q2-pro-start-end-video",
"prompt": "Smooth transition between scenes",
"image_list": ["https://example.com/first-frame.jpg", "https://example.com/last-frame.jpg"],
"seconds": 4
}
Alibaba HappyHorse
The current HappyHorse family is available through Alibaba Cloud and fal.ai under the same customer-facing model IDs:
| Model |
Operation |
Required media |
happyhorse-1.1-t2v |
Text-to-video |
None |
happyhorse-1.1-i2v |
First-frame image-to-video |
image_url |
happyhorse-1.1-r2v |
Reference-to-video |
image_list or video_list |
happyhorse-1.0-video-edit |
Video editing |
video_url and input_seconds |
curl -X POST "https://api.llmapi.ai/v1/videos" \
-H "Authorization: Bearer $LLM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "happyhorse-1.1-t2v",
"prompt": "A paper horse gallops through a sunlit miniature city",
"seconds": 5,
"size": "1280x720",
"seed": 42
}'
HappyHorse 1.1 accepts 3–15 second outputs at 720p or 1080p. Video edit accepts a public source video and bills both input and output duration. Alibaba supports prompt extension, negative prompts, watermark control, reference videos, and reference voices where the selected variant advertises them. fal.ai supports the common text/image path but has a narrower parameter set; use the model catalog's provider mappings to inspect supported parameters before forcing a specific provider.
Request Parameters
Core Parameters
| Parameter |
Type |
Required |
Description |
model |
string |
✓ |
Model ID (e.g. kling-v2.1-master-t2v) |
prompt |
string |
✓ |
Text description of the video |
seconds |
integer | string |
— |
Duration in seconds. Defaults to the model's default. Both 5 and "5" are accepted. |
size |
string |
— |
Resolution as WIDTHxHEIGHT (e.g. 1280x720) |
width |
integer |
— |
Explicit width (alternative to size) |
height |
integer |
— |
Explicit height (alternative to size) |
fps |
integer |
— |
Frames per second (model-dependent) |
seed |
integer |
— |
Reproducibility seed |
image_url |
string |
— |
Reference image for I2V models |
input_reference |
object |
— |
OpenAI-compatible alias — { "image_url": "..." }. Only image_url is supported; file_id (Files API) is not implemented. |
image_list |
string[] |
— |
Image array for multi-image / start-end I2V models |
webhook |
string |
— |
URL to POST the completed job to |
Model-Specific Parameters
These parameters are only meaningful for models that support them. Unsupported parameters are silently ignored.
| Parameter |
Type |
Default |
Supported By |
quality |
string |
— |
Kling, Pixverse, Sora — e.g. "basic", "high" |
options |
string |
— |
Higgsfield — "dop-lite" | "dop-turbo" | "dop-preview" |
thinking_type |
string |
"auto" |
Pixverse v6 — "auto" | "enabled" | "disabled" |
generate_audio |
boolean |
true |
Seedance v1.5, Kling v3, LTX 2 Pro — auto-generate audio track |
sound |
boolean |
true |
Kling v2.6 Pro — include sound |
keep_original_sound |
boolean |
true |
Kling O1 — preserve audio from source |
audio |
boolean |
true |
Pixverse v5.5 — include audio track |
generate_audio_switch |
boolean |
false |
Pixverse v6 — toggle audio generation |
go_fast |
boolean |
true |
MiniMax Hailuo Fast — speed optimisation |
remove_watermark |
boolean |
true |
Sora 2, Seedance 2 — strip provider watermark |
multi_clip |
boolean |
false |
Pixverse v5.5 — generate multiple clips |
generate_multi_clip_switch |
boolean |
false |
Pixverse v6 Transition |
bgm |
boolean |
true |
Vidu start-end — background music |
num_videos |
integer |
1 |
Midjourney v7 I2V — number of outputs |
Status Values
| Status |
Description |
queued |
Job accepted, waiting in provider queue |
in_progress |
Video is being generated |
completed |
Video is ready — video_url is populated |
failed |
Generation failed — error.message explains why |
Response Fields
GET /v1/videos/{id} returns:
| Field |
Type |
Description |
id |
string |
Job ID |
object |
string |
Always "video" (OpenAI Sora convention) |
status |
string |
queued | in_progress | completed | failed |
progress |
integer |
0 or 100 |
model |
string |
Model ID used |
seconds |
string |
Requested duration as a string (OpenAI-compatible) |
size |
string |
Requested resolution as WIDTHxHEIGHT |
usage |
object |
{ duration_seconds } — OpenAI-compatible usage object |
provider_job_id |
string |
Provider-assigned ID (useful for debugging) |
video_url |
string |
Direct download URL (set when completed) |
format |
string |
Always "mp4" |
duration_seconds |
float |
Output duration (set when completed) |
width |
integer |
Output width |
height |
integer |
Output height |
fps |
integer |
Output FPS |
cost |
float |
Cost in USD |
created_at |
unix ts |
Submission timestamp |
completed_at |
unix ts |
Completion timestamp |
error |
object |
{ message } — present on failure |
Choosing a Model
| Need |
Recommended Model |
Notes |
| Best quality T2V |
veo3.1-t2v |
Google Veo 3.1, up to 8s, native audio |
| Best quality I2V |
kling-v2.1-master-i2v |
Kling flagship I2V |
| Fastest & cheapest |
hunyuan-fast-t2v |
HunYuan Fast |
| Long video (up to 30s) |
grok-t2v |
xAI Grok |
| Most aspect ratios |
seedance-v1.5-pro-t2v |
6 ratios, audio support |
| Budget flagship |
wan2.6-t2v |
Alibaba WAN 2.6 |
| Creative transitions |
pixverse-v6-transition |
Start-end frame morphing |
| Music video feel |
vidu-q2-pro-start-end-video |
BGM + start/end frames |
| Image reference |
kling-o1-reference-to-video |
Keep original sound |
| Batch output |
midjourney-v7-i2v |
num_videos up to N |
Use GET /internal/models?type=video to get the full list with supported durations, resolutions, and aspect ratios.
Using the OpenAI SDK
The API is drop-in compatible with OpenAI's Sora video endpoint — point the official openai SDK at https://api.llmapi.ai/v1 and call client.videos.create / client.videos.retrieve / client.videos.download_content directly. You get access to every supported model (Kling, Veo, Seedance, Sora, Runway, WAN, Pixverse, Vidu, Grok, LTX, HunYuan and more) without changing SDK.
Python (OpenAI SDK ≥ 1.60)
import time
from openai import OpenAI
client = OpenAI(
base_url="https://api.llmapi.ai/v1",
api_key="YOUR_LLMAPI_KEY",
)
# Submit — any LLM API video model works here, not just Sora.
video = client.videos.create(
model="kling-v2.1-master-t2v",
prompt="A robot learning to dance in a neon-lit warehouse",
size="1280x720",
seconds="5",
)
print(f"Job {video.id} ({video.status})")
# Poll
while video.status not in ("completed", "failed"):
time.sleep(10)
video = client.videos.retrieve(video.id)
print(f" {video.status} — {video.progress}%")
if video.status == "failed":
raise RuntimeError(video.error.message)
# Download — streams MP4 bytes through the gateway (no CDN auth needed)
content = client.videos.download_content(video.id)
content.write_to_file("output.mp4")
print("Saved to output.mp4")
Image-to-Video with the OpenAI SDK
Use input_reference (an OpenAI-compatible alias for image_url) with any -i2v model:
video = client.videos.create(
model="kling-v2.1-master-i2v",
prompt="The cat slowly turns its head and blinks",
seconds="5",
extra_body={
"input_reference": {"image_url": "https://example.com/cat.jpg"},
},
)
input_reference must contain image_url — the OpenAI Files API (file_id) is not supported. Pass a publicly reachable image URL instead. Model-specific params like image_list, quality, generate_audio, and other flags go through extra_body because they're outside the OpenAI typed SDK surface.
Node.js (OpenAI SDK ≥ 4.70)
import OpenAI from "openai";
import { writeFile } from "node:fs/promises";
const client = new OpenAI({
baseURL: "https://api.llmapi.ai/v1",
apiKey: process.env.LLMAPI_KEY!,
});
let video = await client.videos.create({
model: "veo3.1-fast-t2v",
prompt: "A time-lapse of a sunflower blooming",
seconds: "8",
size: "1920x1080",
});
while (video.status === "queued" || video.status === "in_progress") {
await new Promise((r) => setTimeout(r, 10_000));
video = await client.videos.retrieve(video.id);
console.log(`${video.status} (${video.progress}%)`);
}
if (video.status === "failed") throw new Error(video.error?.message);
const res = await client.videos.downloadContent(video.id);
await writeFile("output.mp4", Buffer.from(await res.arrayBuffer()));
Python Example (Plain requests)
If you'd rather avoid the OpenAI SDK:
import time
import requests
BASE_URL = "https://api.llmapi.ai/v1"
HEADERS = {"Authorization": "Bearer YOUR_KEY", "Content-Type": "application/json"}
# Submit
resp = requests.post(f"{BASE_URL}/videos", headers=HEADERS, json={
"model": "kling-v2.1-master-t2v",
"prompt": "A robot learning to dance in a neon-lit warehouse",
"seconds": 5,
})
resp.raise_for_status()
job_id = resp.json()["id"]
print(f"Job submitted: {job_id}")
# Poll until done
while True:
status = requests.get(f"{BASE_URL}/videos/{job_id}", headers=HEADERS).json()
print(f"Status: {status['status']}")
if status["status"] == "completed":
print(f"Video URL: {status['video_url']}")
break
if status["status"] == "failed":
raise RuntimeError(status.get("error", {}).get("message", "Unknown error"))
time.sleep(10)
# Download
video = requests.get(f"{BASE_URL}/videos/{job_id}/content", headers=HEADERS)
with open("output.mp4", "wb") as f:
f.write(video.content)
print("Saved to output.mp4")
Billing
- Video generation is billed at the model's rate based on requested parameters
- Cost is calculated and locked in at submission time based on the requested parameters (seconds, resolution, quality, and other model-specific options)
- Failed jobs are not charged
- Billing applies regardless of whether you use
video_url directly or stream via /content