Try Kling O3 Omni in the Workbench
Run this model interactively, tune parameters, and compare outputs.
kling-video-o3-omni
Kling Video 3.0 Omni is a single model for text-to-video, image-to-video with optional start and end frames, reference images, reference-video guided generation, and video editing. Pick 720p, 1080p, or 4K output resolution in one form.
Example request
Use the Workbench as a request builder: configure parameters for this model in the UI, then open the API tab to copy the exact cURL or Python call.
- Sync
- Async
- Async with SSE
This blocks until the video is ready (typically 5-15 minutes). Prefer Async or Async with SSE for anything beyond quick experimentation.See the video generation reference for more details.
- Minimal
- Basic parameters
- All parameters
curl -X POST https://hub.oxen.ai/api/ai/videos/generate \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OXEN_API_KEY" \
-d '{
"model": "kling-video-o3-omni",
"prompt": "<prompt>"
}'
import os
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/videos/generate",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {os.environ['OXEN_API_KEY']}",
},
json={
"model": "kling-video-o3-omni",
"prompt": "<prompt>"
},
)
response.raise_for_status()
print(response.json())
curl -X POST https://hub.oxen.ai/api/ai/videos/generate \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OXEN_API_KEY" \
-d '{
"model": "kling-video-o3-omni",
"prompt": "<prompt>",
"input_image": [
"https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png"
],
"input_video": "https://hub.oxen.ai/api/repos/ox/Oxen-AI-Assets/file/main/images/winter_summer_ox.mp4",
"video_mode": "edit",
"start_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"tail_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"elements": [
{}
]
}'
import os
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/videos/generate",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {os.environ['OXEN_API_KEY']}",
},
json={
"model": "kling-video-o3-omni",
"prompt": "<prompt>",
"input_image": [
"https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png"
],
"input_video": "https://hub.oxen.ai/api/repos/ox/Oxen-AI-Assets/file/main/images/winter_summer_ox.mp4",
"video_mode": "edit",
"start_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"tail_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"elements": [
{}
]
},
)
response.raise_for_status()
print(response.json())
curl -X POST https://hub.oxen.ai/api/ai/videos/generate \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OXEN_API_KEY" \
-d '{
"model": "kling-video-o3-omni",
"prompt": "<prompt>",
"input_image": [
"https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png"
],
"input_video": "https://hub.oxen.ai/api/repos/ox/Oxen-AI-Assets/file/main/images/winter_summer_ox.mp4",
"video_mode": "edit",
"start_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"tail_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"elements": [
{}
],
"resolution": "1080p",
"duration": 5,
"aspect_ratio": "16:9",
"generate_audio": false,
"keep_audio": true
}'
import os
import requests
response = requests.post(
"https://hub.oxen.ai/api/ai/videos/generate",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {os.environ['OXEN_API_KEY']}",
},
json={
"model": "kling-video-o3-omni",
"prompt": "<prompt>",
"input_image": [
"https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png"
],
"input_video": "https://hub.oxen.ai/api/repos/ox/Oxen-AI-Assets/file/main/images/winter_summer_ox.mp4",
"video_mode": "edit",
"start_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"tail_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"elements": [
{}
],
"resolution": "1080p",
"duration": 5,
"aspect_ratio": "16:9",
"generate_audio": false,
"keep_audio": true
},
)
response.raise_for_status()
print(response.json())
See the async queue reference for more details.
- Minimal
- Basic parameters
- All parameters
# Enqueue, capture the generation id.
GEN_ID=$(curl -s -X POST https://hub.oxen.ai/api/ai/queue \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OXEN_API_KEY" \
-d '{
"model": "kling-video-o3-omni",
"prompt": "<prompt>"
}' | jq -r '.generations[0].generation_id')
# Poll until the generation reaches a terminal status.
while true; do
STATUS=$(curl -s -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/queue/$GEN_ID" | jq -r '.status')
echo "Status: $STATUS"
case $STATUS in succeeded|failed|cancelled) break;; esac
sleep 5
done
# Print the result.
curl -s -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/queue/$GEN_ID" | jq .
import os
import time
import requests
HEADERS = {
"Content-Type": "application/json",
"Authorization": f"Bearer {os.environ['OXEN_API_KEY']}",
}
enqueue = requests.post(
"https://hub.oxen.ai/api/ai/queue",
headers=HEADERS,
json={
"model": "kling-video-o3-omni",
"prompt": "<prompt>"
},
)
enqueue.raise_for_status()
generation_id = enqueue.json()["generations"][0]["generation_id"]
while True:
data = requests.get(
f"https://hub.oxen.ai/api/ai/queue/{generation_id}",
headers=HEADERS,
).json()
if data["status"] in {"succeeded", "failed", "cancelled"}:
break
time.sleep(5)
if data["status"] == "succeeded":
print(f"Result: {data['result_url']}")
else:
print(f"Generation {data['status']}: {data.get('error_message')}")
# Enqueue, capture the generation id.
GEN_ID=$(curl -s -X POST https://hub.oxen.ai/api/ai/queue \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OXEN_API_KEY" \
-d '{
"model": "kling-video-o3-omni",
"prompt": "<prompt>",
"input_image": [
"https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png"
],
"input_video": "https://hub.oxen.ai/api/repos/ox/Oxen-AI-Assets/file/main/images/winter_summer_ox.mp4",
"video_mode": "edit",
"start_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"tail_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"elements": [
{}
]
}' | jq -r '.generations[0].generation_id')
# Poll until the generation reaches a terminal status.
while true; do
STATUS=$(curl -s -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/queue/$GEN_ID" | jq -r '.status')
echo "Status: $STATUS"
case $STATUS in succeeded|failed|cancelled) break;; esac
sleep 5
done
# Print the result.
curl -s -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/queue/$GEN_ID" | jq .
import os
import time
import requests
HEADERS = {
"Content-Type": "application/json",
"Authorization": f"Bearer {os.environ['OXEN_API_KEY']}",
}
enqueue = requests.post(
"https://hub.oxen.ai/api/ai/queue",
headers=HEADERS,
json={
"model": "kling-video-o3-omni",
"prompt": "<prompt>",
"input_image": [
"https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png"
],
"input_video": "https://hub.oxen.ai/api/repos/ox/Oxen-AI-Assets/file/main/images/winter_summer_ox.mp4",
"video_mode": "edit",
"start_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"tail_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"elements": [
{}
]
},
)
enqueue.raise_for_status()
generation_id = enqueue.json()["generations"][0]["generation_id"]
while True:
data = requests.get(
f"https://hub.oxen.ai/api/ai/queue/{generation_id}",
headers=HEADERS,
).json()
if data["status"] in {"succeeded", "failed", "cancelled"}:
break
time.sleep(5)
if data["status"] == "succeeded":
print(f"Result: {data['result_url']}")
else:
print(f"Generation {data['status']}: {data.get('error_message')}")
# Enqueue, capture the generation id.
GEN_ID=$(curl -s -X POST https://hub.oxen.ai/api/ai/queue \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OXEN_API_KEY" \
-d '{
"model": "kling-video-o3-omni",
"prompt": "<prompt>",
"input_image": [
"https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png"
],
"input_video": "https://hub.oxen.ai/api/repos/ox/Oxen-AI-Assets/file/main/images/winter_summer_ox.mp4",
"video_mode": "edit",
"start_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"tail_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"elements": [
{}
],
"resolution": "1080p",
"duration": 5,
"aspect_ratio": "16:9",
"generate_audio": false,
"keep_audio": true
}' | jq -r '.generations[0].generation_id')
# Poll until the generation reaches a terminal status.
while true; do
STATUS=$(curl -s -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/queue/$GEN_ID" | jq -r '.status')
echo "Status: $STATUS"
case $STATUS in succeeded|failed|cancelled) break;; esac
sleep 5
done
# Print the result.
curl -s -H "Authorization: Bearer $OXEN_API_KEY" \
"https://hub.oxen.ai/api/ai/queue/$GEN_ID" | jq .
import os
import time
import requests
HEADERS = {
"Content-Type": "application/json",
"Authorization": f"Bearer {os.environ['OXEN_API_KEY']}",
}
enqueue = requests.post(
"https://hub.oxen.ai/api/ai/queue",
headers=HEADERS,
json={
"model": "kling-video-o3-omni",
"prompt": "<prompt>",
"input_image": [
"https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png"
],
"input_video": "https://hub.oxen.ai/api/repos/ox/Oxen-AI-Assets/file/main/images/winter_summer_ox.mp4",
"video_mode": "edit",
"start_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"tail_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"elements": [
{}
],
"resolution": "1080p",
"duration": 5,
"aspect_ratio": "16:9",
"generate_audio": false,
"keep_audio": true
},
)
enqueue.raise_for_status()
generation_id = enqueue.json()["generations"][0]["generation_id"]
while True:
data = requests.get(
f"https://hub.oxen.ai/api/ai/queue/{generation_id}",
headers=HEADERS,
).json()
if data["status"] in {"succeeded", "failed", "cancelled"}:
break
time.sleep(5)
if data["status"] == "succeeded":
print(f"Result: {data['result_url']}")
else:
print(f"Generation {data['status']}: {data.get('error_message')}")
See the async queue reference for more details.
- Minimal
- Basic parameters
- All parameters
# Enqueue, capture the generation id.
GEN_ID=$(curl -s -X POST https://hub.oxen.ai/api/ai/queue \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OXEN_API_KEY" \
-d '{
"model": "kling-video-o3-omni",
"prompt": "<prompt>"
}' | jq -r '.generations[0].generation_id')
# Stream the SSE channel, grab the data line that follows a
# media_generation_completed event for our id, and pretty-print it.
curl -sN -H "Authorization: Bearer $OXEN_API_KEY" https://hub.oxen.ai/api/events \
| awk -v id="$GEN_ID" '
/^event: media_generation_completed$/ { expect=1; next }
/^data: / && expect {
payload = substr($0, 7)
if (index(payload, "\"generation_id\":\"" id "\"")) { print payload; exit }
expect = 0
}
' | jq .
import json
import os
import requests
API_KEY = os.environ["OXEN_API_KEY"]
AUTH = {"Authorization": f"Bearer {API_KEY}"}
enqueue = requests.post(
"https://hub.oxen.ai/api/ai/queue",
headers={**AUTH, "Content-Type": "application/json"},
json={
"model": "kling-video-o3-omni",
"prompt": "<prompt>"
},
)
enqueue.raise_for_status()
generation_id = enqueue.json()["generations"][0]["generation_id"]
with requests.get(
"https://hub.oxen.ai/api/events",
headers=AUTH,
stream=True,
) as stream:
event_name = None
for line in stream.iter_lines(decode_unicode=True):
if line.startswith("event: "):
event_name = line.removeprefix("event: ")
elif line.startswith("data: ") and event_name == "media_generation_completed":
payload = json.loads(line.removeprefix("data: "))
if payload.get("generation_id") == generation_id:
print(payload)
break
# Enqueue, capture the generation id.
GEN_ID=$(curl -s -X POST https://hub.oxen.ai/api/ai/queue \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OXEN_API_KEY" \
-d '{
"model": "kling-video-o3-omni",
"prompt": "<prompt>",
"input_image": [
"https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png"
],
"input_video": "https://hub.oxen.ai/api/repos/ox/Oxen-AI-Assets/file/main/images/winter_summer_ox.mp4",
"video_mode": "edit",
"start_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"tail_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"elements": [
{}
]
}' | jq -r '.generations[0].generation_id')
# Stream the SSE channel, grab the data line that follows a
# media_generation_completed event for our id, and pretty-print it.
curl -sN -H "Authorization: Bearer $OXEN_API_KEY" https://hub.oxen.ai/api/events \
| awk -v id="$GEN_ID" '
/^event: media_generation_completed$/ { expect=1; next }
/^data: / && expect {
payload = substr($0, 7)
if (index(payload, "\"generation_id\":\"" id "\"")) { print payload; exit }
expect = 0
}
' | jq .
import json
import os
import requests
API_KEY = os.environ["OXEN_API_KEY"]
AUTH = {"Authorization": f"Bearer {API_KEY}"}
enqueue = requests.post(
"https://hub.oxen.ai/api/ai/queue",
headers={**AUTH, "Content-Type": "application/json"},
json={
"model": "kling-video-o3-omni",
"prompt": "<prompt>",
"input_image": [
"https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png"
],
"input_video": "https://hub.oxen.ai/api/repos/ox/Oxen-AI-Assets/file/main/images/winter_summer_ox.mp4",
"video_mode": "edit",
"start_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"tail_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"elements": [
{}
]
},
)
enqueue.raise_for_status()
generation_id = enqueue.json()["generations"][0]["generation_id"]
with requests.get(
"https://hub.oxen.ai/api/events",
headers=AUTH,
stream=True,
) as stream:
event_name = None
for line in stream.iter_lines(decode_unicode=True):
if line.startswith("event: "):
event_name = line.removeprefix("event: ")
elif line.startswith("data: ") and event_name == "media_generation_completed":
payload = json.loads(line.removeprefix("data: "))
if payload.get("generation_id") == generation_id:
print(payload)
break
# Enqueue, capture the generation id.
GEN_ID=$(curl -s -X POST https://hub.oxen.ai/api/ai/queue \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OXEN_API_KEY" \
-d '{
"model": "kling-video-o3-omni",
"prompt": "<prompt>",
"input_image": [
"https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png"
],
"input_video": "https://hub.oxen.ai/api/repos/ox/Oxen-AI-Assets/file/main/images/winter_summer_ox.mp4",
"video_mode": "edit",
"start_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"tail_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"elements": [
{}
],
"resolution": "1080p",
"duration": 5,
"aspect_ratio": "16:9",
"generate_audio": false,
"keep_audio": true
}' | jq -r '.generations[0].generation_id')
# Stream the SSE channel, grab the data line that follows a
# media_generation_completed event for our id, and pretty-print it.
curl -sN -H "Authorization: Bearer $OXEN_API_KEY" https://hub.oxen.ai/api/events \
| awk -v id="$GEN_ID" '
/^event: media_generation_completed$/ { expect=1; next }
/^data: / && expect {
payload = substr($0, 7)
if (index(payload, "\"generation_id\":\"" id "\"")) { print payload; exit }
expect = 0
}
' | jq .
import json
import os
import requests
API_KEY = os.environ["OXEN_API_KEY"]
AUTH = {"Authorization": f"Bearer {API_KEY}"}
enqueue = requests.post(
"https://hub.oxen.ai/api/ai/queue",
headers={**AUTH, "Content-Type": "application/json"},
json={
"model": "kling-video-o3-omni",
"prompt": "<prompt>",
"input_image": [
"https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png"
],
"input_video": "https://hub.oxen.ai/api/repos/ox/Oxen-AI-Assets/file/main/images/winter_summer_ox.mp4",
"video_mode": "edit",
"start_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"tail_image_url": "https://hub.oxen.ai/api/repos/elau/assets/file/main/bloxy/bloxy_cropped_512x512.png",
"elements": [
{}
],
"resolution": "1080p",
"duration": 5,
"aspect_ratio": "16:9",
"generate_audio": false,
"keep_audio": true
},
)
enqueue.raise_for_status()
generation_id = enqueue.json()["generations"][0]["generation_id"]
with requests.get(
"https://hub.oxen.ai/api/events",
headers=AUTH,
stream=True,
) as stream:
event_name = None
for line in stream.iter_lines(decode_unicode=True):
if line.startswith("event: "):
event_name = line.removeprefix("event: ")
elif line.startswith("data: ") and event_name == "media_generation_completed":
payload = json.loads(line.removeprefix("data: "))
if payload.get("generation_id") == generation_id:
print(payload)
break
Fetch model details
The models endpoint returns the full model object, including itsjson_request_schema.
curl -H "Authorization: Bearer $OXEN_API_KEY" https://hub.oxen.ai/api/ai/models/kling-video-o3-omni
Request parameters
Required parameters
| Field | Type | Default | Description |
|---|---|---|---|
prompt | string | — | Generation or edit instructions. Use @Image1, @Image2 for reference images and @Element1, @Element2 for elements. |
Optional parameters
| Field | Type | Default | Description |
|---|---|---|---|
input_image | array<string> | — | Optional reference images for style or subject. Use @Image1, @Image2 in the prompt. Counts toward the shared limit of 7 with elements (4 when input video is set). |
input_video | string | — | Source video for edit or motion/style reference. Format: uri. |
video_mode | string | "edit" | Edit applies your prompt to the clip. Reference uses it for motion/style only. One of: edit, reference. |
start_image_url | string | — | Optional first frame. End frame requires a start frame. Format: uri. |
tail_image_url | string | — | Optional last frame. Requires start frame. Format: uri. |
elements | array<object> | — | Optional element references. Use @Element1, @Element2 in the prompt. Max 7 reference images and elements total, max 4 when using an input video. |
resolution | string | "1080p" | Output resolution tier. One of: 720p, 1080p, 4k. |
duration | integer | 5 | Output length in seconds (3 to 15). Ignored for video edit; output matches input length. Range: 3 – 15. |
aspect_ratio | string | "16:9" | Output aspect ratio. Omit when editing video or using only start/end frames. One of: 16:9, 9:16, 1:1. |
generate_audio | boolean | false | Generate native audio. Must be off when a reference or input video is provided. |
keep_audio | boolean | true | When editing or referencing a video, keep the source audio. |