cURL
Simple Text-to-Video
Copy
curl -X POST https://platform.runblob.io/v1/kling/o3-video/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "A cat walking in a garden"
}'
Text-to-Video with Custom Settings
Copy
curl -X POST https://platform.runblob.io/v1/kling/o3-video/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Futuristic city with flying cars, neon lights, rain",
"duration": "10",
"aspect_ratio": "16:9",
"model": "kling_o3_pro"
}'
Image-to-Video
Copy
curl -X POST https://platform.runblob.io/v1/kling/o3-video/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Make the character wave and smile",
"images_url": [
"https://example.com/character.jpg"
],
"duration": "5",
"aspect_ratio": "9:16",
"model": "kling_o3"
}'
Portrait Video for Stories
Copy
curl -X POST https://platform.runblob.io/v1/kling/o3-video/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Person dancing energetically",
"duration": "5",
"aspect_ratio": "9:16",
"model": "kling_o3_pro"
}'
Multi-Shots Product Showcase
Copy
curl -X POST https://platform.runblob.io/v1/kling/o3-video/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Product showcase from different angles",
"duration": "15",
"model": "kling_o3_pro"
}'
With Webhook
Copy
curl -X POST https://platform.runblob.io/v1/kling/o3-video/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Dragon flying over mountains",
"duration": "10",
"model": "kling_o3_pro",
"callback_url": "https://your-app.com/webhook/o3-video"
}'
Check Status
Copy
curl -X GET https://platform.runblob.io/v1/kling/o3-video/generations/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer YOUR_API_KEY"
Python
Simple Client
Copy
import requests
import time
from typing import Optional, Dict
class KlingO3VideoClient:
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://platform.runblob.io"
self.headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
def generate(self, prompt: str, **kwargs) -> Dict:
"""Generate video with O3 model"""
response = requests.post(
f"{self.base_url}/v1/kling/o3-video/generate",
headers=self.headers,
json={"prompt": prompt, **kwargs}
)
response.raise_for_status()
return response.json()
def get_status(self, generation_id: str) -> Dict:
"""Get generation status"""
response = requests.get(
f"{self.base_url}/v1/kling/o3-video/generations/{generation_id}",
headers=self.headers
)
response.raise_for_status()
return response.json()
def wait_for_completion(
self,
generation_id: str,
max_wait: int = 900 # 15 minutes
) -> str:
"""Poll until video is ready"""
intervals = [5, 5, 10, 15, 15] # Progressive backoff
start_time = time.time()
attempt = 0
while time.time() - start_time < max_wait:
result = self.get_status(generation_id)
if result["status"] == "completed":
return result["video_url"]
elif result["status"] == "failed":
raise Exception("Generation failed")
delay = intervals[min(attempt, len(intervals) - 1)]
time.sleep(delay)
attempt += 1
raise TimeoutError("Generation timed out")
# Usage
client = KlingO3VideoClient("YOUR_API_KEY")
# Simple generation
result = client.generate(
"Dragon flying over mountains at sunset",
duration="10",
model="kling_o3_pro"
)
print(f"Generation ID: {result['generation_id']}")
# Wait for completion
video_url = client.wait_for_completion(result["generation_id"])
print(f"Video ready: {video_url}")
Image-to-Video with Base64
Copy
import base64
def encode_image(image_path: str) -> str:
"""Encode image to base64"""
with open(image_path, "rb") as f:
return f"data:image/jpeg;base64,{base64.b64encode(f.read()).decode()}"
# Upload local image
client = KlingO3VideoClient("YOUR_API_KEY")
result = client.generate(
"Animate this photo with smooth motion",
images_base64=[encode_image("photo.jpg")],
duration="5",
mode="std"
)
video_url = client.wait_for_completion(result["generation_id"])
print(f"Video ready: {video_url}")
Advanced: With Error Handling
Copy
import requests
from typing import Optional
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class O3VideoClient:
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://platform.runblob.io"
def generate(self, prompt: str, **kwargs) -> dict:
"""Generate video with comprehensive error handling"""
try:
# Validate inputs
self._validate_request(prompt, **kwargs)
response = requests.post(
f"{self.base_url}/v1/kling/o3-video/generate",
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
},
json={"prompt": prompt, **kwargs},
timeout=30
)
if response.status_code == 402:
raise ValueError("INSUFFICIENT_CREDITS: Please top up your balance")
elif response.status_code == 400:
error_detail = response.json().get("detail", "Unknown error")
raise ValueError(f"Invalid request: {error_detail}")
response.raise_for_status()
result = response.json()
logger.info(f"✅ Generation started: {result['generation_id']}")
return result
except requests.exceptions.HTTPError as e:
logger.error(f"HTTP error: {e}")
raise
except Exception as e:
logger.error(f"Error: {e}")
raise
def _validate_request(self, prompt: str, **kwargs):
"""Validate request parameters"""
if len(prompt) < 1 or len(prompt) > 2500:
raise ValueError("Prompt must be 1-2500 characters")
images_url = kwargs.get("images_url", [])
images_base64 = kwargs.get("images_base64", [])
total_images = len(images_url) + len(images_base64)
if total_images > 5:
raise ValueError("Maximum 5 images allowed")
if images_url and images_base64:
raise ValueError("Cannot provide both images_url and images_base64")
duration = kwargs.get("duration")
if duration and duration not in ["5", "10", "15"]:
raise ValueError("Duration must be 5, 10, or 15 seconds")
mode = kwargs.get("mode")
if mode and mode not in ["std", "pro"]:
raise ValueError("Mode must be 'std' or 'pro'")
# Usage with error handling
try:
client = O3VideoClient("YOUR_API_KEY")
result = client.generate(
"Epic fantasy landscape with castles",
duration="15",
mode="pro"
)
print(f"✅ Generation started: {result['generation_id']}")
except ValueError as e:
print(f"❌ Validation error: {e}")
except Exception as e:
print(f"❌ Unexpected error: {e}")
JavaScript / Node.js
Simple Client
Copy
const fetch = require('node-fetch');
class KlingO3VideoClient {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://platform.runblob.io';
}
async generate(prompt, options = {}) {
const response = await fetch(`${this.baseUrl}/v1/kling/o3-video/generate`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ prompt, ...options })
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Generation failed: ${error.detail || 'Unknown error'}`);
}
return await response.json();
}
async getStatus(generationId) {
const response = await fetch(
`${this.baseUrl}/v1/kling/o3-video/generations/${generationId}`,
{
headers: { 'Authorization': `Bearer ${this.apiKey}` }
}
);
return await response.json();
}
async waitForCompletion(generationId, maxWait = 900000) { // 15 minutes
const intervals = [5000, 5000, 10000, 15000, 15000]; // Progressive backoff
const startTime = Date.now();
let attempt = 0;
while (Date.now() - startTime < maxWait) {
const result = await this.getStatus(generationId);
if (result.status === 'completed') {
return result.video_url;
} else if (result.status === 'failed') {
throw new Error('Generation failed');
}
const delay = intervals[Math.min(attempt, intervals.length - 1)];
await new Promise(resolve => setTimeout(resolve, delay));
attempt++;
}
throw new Error('Generation timed out');
}
}
// Usage
(async () => {
const client = new KlingO3VideoClient('YOUR_API_KEY');
// Simple generation
const result = await client.generate(
'Dragon flying over mountains at sunset',
{
duration: '10',
model: 'kling_o3_pro'
}
);
console.log('Generation ID:', result.generation_id);
// Wait for completion
const videoUrl = await client.waitForCompletion(result.generation_id);
console.log('Video ready:', videoUrl);
})();
Image-to-Video with URLs
Copy
const client = new KlingO3VideoClient('YOUR_API_KEY');
const result = await client.generate(
'Make the character wave and smile',
{
images_url: [
'https://example.com/character.jpg'
],
duration: '5',
aspect_ratio: '9:16',
model: 'kling_o3'
}
);
const videoUrl = await client.waitForCompletion(result.generation_id);
console.log('✅ Video ready:', videoUrl);
With Base64 Upload
Copy
const fs = require('fs').promises;
async function encodeImage(imagePath) {
const buffer = await fs.readFile(imagePath);
return `data:image/jpeg;base64,${buffer.toString('base64')}`;
}
// Upload local images
const result = await client.generate(
'Animate these photos with smooth transitions',
{
images_base64: [
await encodeImage('photo1.jpg'),
await encodeImage('photo2.jpg')
],
duration: '10',
model: 'kling_o3_pro'
}
);
TypeScript with Types
Copy
type VideoDuration = '5' | '10' | '15';
type VideoModel = 'kling_o3' | 'kling_o3_pro';
type VideoAspectRatio = '16:9' | '9:16' | '1:1';
type VideoStatus = 'pending' | 'processing' | 'completed' | 'failed';
interface GenerateO3VideoRequest {
prompt: string;
images_base64?: string[];
images_url?: string[];
duration?: VideoDuration;
aspect_ratio?: VideoAspectRatio;
model?: VideoModel;
keep_original_sound?: boolean;
callback_url?: string;
}
interface GenerateO3VideoResponse {
generation_id: string;
status: VideoStatus;
price: string;
}
interface O3VideoStatusResponse {
generation_id: string;
status: VideoStatus;
prompt: string;
video_url: string | null;
model: string;
}
class TypedO3VideoClient {
constructor(private apiKey: string) {}
async generate(request: GenerateO3VideoRequest): Promise<GenerateO3VideoResponse> {
const response = await fetch('https://platform.runblob.io/v1/kling/o3-video/generate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
return await response.json();
}
async getStatus(generationId: string): Promise<O3VideoStatusResponse> {
const response = await fetch(
`https://platform.runblob.io/v1/kling/o3-video/generations/${generationId}`,
{
headers: { 'Authorization': `Bearer ${this.apiKey}` }
}
);
return await response.json();
}
}
// Usage
const client = new TypedO3VideoClient('YOUR_API_KEY');
const result = await client.generate({
prompt: 'Dragon flying over mountains',
duration: '10',
model: 'kling_o3_pro',
aspect_ratio: '16:9'
});
console.log('Generation started:', result.generation_id);
Using Webhooks (Recommended)
Copy
const express = require('express');
const app = express();
app.use(express.json());
// Webhook endpoint
app.post('/webhook/o3-video', (req, res) => {
const { generation_id, status, video_url, model, message } = req.body;
if (status === 'completed') {
console.log(`✅ Video ready for ${generation_id}: ${video_url} (${model})`);
// Process the completed video
processVideo(generation_id, video_url, model);
} else {
console.error(`❌ Generation failed for ${generation_id}: ${message}`);
// Handle the error
handleError(generation_id, message);
}
res.status(200).json({ status: 'received' });
});
function processVideo(generationId, videoUrl, model) {
// Download video to your storage
// Update database
// Notify user via WebSocket/SSE
}
function handleError(generationId, errorCode) {
// Log error for analytics
// Update database
// Notify user
}
// Generate with webhook
const client = new KlingO3VideoClient('YOUR_API_KEY');
const result = await client.generate(
'Epic fantasy landscape',
{
duration: '15',
model: 'kling_o3_pro',
callback_url: 'https://your-app.com/webhook/o3-video'
}
);
console.log('Generation started:', result.generation_id);
// Webhook will notify when complete
app.listen(3000);
Best Practices
Use Webhooks for Production
Use Webhooks for Production
Polling is inefficient and can hit rate limits. Use webhooks to receive instant notifications:
Copy
result = client.generate(
prompt,
duration="10",
callback_url="https://your-app.com/webhook/o3-video"
)
Validate Input Before Sending
Validate Input Before Sending
Check parameters before sending to catch errors early:
Copy
function validateRequest(data) {
if (data.prompt.length < 1 || data.prompt.length > 2500) {
throw new Error("Invalid prompt length");
}
const totalImages = (data.images_url?.length || 0) +
(data.images_base64?.length || 0);
if (totalImages > 5) {
throw new Error("Maximum 5 images allowed");
}
if (data.images_url && data.images_base64) {
throw new Error("Cannot mix images_url and images_base64");
}
}
Use Progressive Polling
Use Progressive Polling
Start with short intervals, increase gradually:
Copy
intervals = [5, 5, 10, 15, 15] # seconds
for i, delay in enumerate(intervals):
result = check_status(generation_id)
if result['status'] in ['completed', 'failed']:
break
time.sleep(delay)
Download & Store Videos
Download & Store Videos
CDN URLs may expire after 24 hours. Download and save to your storage:
Copy
import requests
def download_video(url: str, save_path: str):
response = requests.get(url, stream=True)
with open(save_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
# After generation completes
download_video(video_url, f'videos/{generation_id}.mp4')
UI Component Examples
Duration Selector
Copy
const durations = [
{ value: '5', label: '5 seconds', time: '~2-3 min' },
{ value: '10', label: '10 seconds', time: '~3-5 min' },
{ value: '15', label: '15 seconds', time: '~5-7 min' },
];
function DurationSelector({ value, onChange }) {
return (
<select value={value} onChange={(e) => onChange(e.target.value)}>
{durations.map(d => (
<option key={d.value} value={d.value}>
{d.label} ({d.time})
</option>
))}
</select>
);
}
Model/Quality Selector
Copy
const models = [
{
value: 'kling_o3',
label: 'Standard (720p)',
description: 'Faster generation, lower cost',
icon: '⚡'
},
{
value: 'kling_o3_pro',
label: 'Pro (1080p)',
description: 'Higher quality, ~2x price',
icon: '⭐'
},
];
function ModelSelector({ value, onChange }) {
return (
<div>
{models.map(model => (
<button
key={mode.value}
onClick={() => onChange(mode.value)}
className={value === mode.value ? 'active' : ''}
>
<span>{mode.icon}</span>
<div>
<strong>{mode.label}</strong>
<p>{mode.description}</p>
</div>
</button>
))}
</div>
);
}
Aspect Ratio Selector
Copy
const aspectRatios = [
{ value: '16:9', label: 'Landscape', icon: '🖼️', use: 'YouTube, Desktop' },
{ value: '9:16', label: 'Portrait', icon: '📱', use: 'Stories, TikTok' },
{ value: '1:1', label: 'Square', icon: '⬜', use: 'Instagram' },
];
function AspectRatioSelector({ value, onChange }) {
return (
<div className="aspect-ratio-grid">
{aspectRatios.map(ratio => (
<div
key={ratio.value}
onClick={() => onChange(ratio.value)}
className={value === ratio.value ? 'selected' : ''}
>
<span className="icon">{ratio.icon}</span>
<strong>{ratio.label}</strong>
<small>{ratio.use}</small>
</div>
))}
</div>
);
}