Video Generation
The full list of models can be viewed on the Models page or retrieved dynamically via the API endpoint: https://openai.bothub.chat/v1/videos/models
BotHub provides a dedicated API for video generation, compatible with asynchronous processing patterns. Video generation is a long-running operation — the request returns immediately with a pending status, and you poll for the result using the video ID.
How It Works
- Send a generation request —
POST /v1/videos/generationsreturns immediately withstatus: "pending"and a videoid. - Poll for status — Periodically call
GET /v1/videos/:videoIduntilstatusbecomes"completed"or"failed". - Download the video — On completion,
unsigned_urlscontains the URL(s) of the generated video.
Endpoints
POST /v1/videos/generations
Creates a new video generation request. Returns immediately — generation happens asynchronously.
Base URL: https://openai.bothub.chat
Authentication: Authorization: Bearer YOUR_BOTHUB_API_KEY
Request Body
| Field | Type | Description |
|---|---|---|
model | string | Model ID (e.g., veo-3.1, kling-v2.6) |
prompt | string | Text description of the video to generate |
imageUrl | string | Input image URL for image-to-video generation (optional) |
notify | boolean | Send email notification on completion (optional) |
settings | object | Generation settings (see below) |
Settings Object
| Field | Type | Description |
|---|---|---|
duration_seconds | number | Video duration: 4, 5, 6, 7, 8, 9, 10, or 12 |
aspect_ratio | string | e.g. "16:9", "9:16", "1:1" |
negative_prompt | string | Things to avoid in the generation |
generate_audio | boolean | Whether to generate audio |
seed | number | Seed for reproducible results |
Each model supports different settings. Refer to the model's documentation on the Models page for the full parameter list.
GET /v1/videos/{videoId}
Returns the current status and result of a video generation request.
| Query Param | Description |
|---|---|
include_usage | Pass "true" to include caps usage in the response |
GET /v1/videos/models
Returns the list of available video generation models.
Text-to-Video
- JS
- Python
- Go
- cURL
async function generateVideo() {
const BASE_URL = 'https://openai.bothub.chat/v1';
const API_KEY = 'YOUR_BOTHUB_API_KEY';
// Step 1: Create the generation request
const createRes = await fetch(`${BASE_URL}/videos/generations`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify({
model: 'veo-3.1',
prompt: 'A time-lapse of a blooming flower in a sunny garden',
notify: false,
settings: {
duration_seconds: 6,
aspect_ratio: '16:9',
},
}),
});
const { id } = await createRes.json();
console.log('Request created, id:', id);
// Step 2: Poll until completed
let video;
while (true) {
await new Promise((r) => setTimeout(r, 10000));
const statusRes = await fetch(`${BASE_URL}/videos/${id}?include_usage=true`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
video = await statusRes.json();
console.log('Status:', video.status);
if (video.status === 'completed' || video.status === 'failed') break;
}
if (video.status === 'completed') {
console.log('Video URL:', video.unsignedUrls[0]);
} else {
console.error('Generation failed:', video.error);
}
}
generateVideo();
import time
import requests
BASE_URL = 'https://openai.bothub.chat/v1'
API_KEY = 'YOUR_BOTHUB_API_KEY'
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {API_KEY}',
}
# Step 1: Create the generation request
response = requests.post(
f'{BASE_URL}/videos/generations',
json={
'model': 'veo-3.1',
'prompt': 'A time-lapse of a blooming flower in a sunny garden',
'notify': False,
'settings': {
'duration_seconds': 6,
'aspect_ratio': '16:9',
},
},
headers=headers,
)
video_id = response.json()['id']
print('Request created, id:', video_id)
# Step 2: Poll until completed
while True:
time.sleep(10)
status_res = requests.get(
f'{BASE_URL}/videos/{video_id}?include_usage=true',
headers=headers,
)
video = status_res.json()
print('Status:', video['status'])
if video['status'] in ('completed', 'failed'):
break
if video['status'] == 'completed':
print('Video URL:', video['unsigned_urls'][0])
else:
print('Generation failed:', video.get('error'))
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
const (
baseURL = "https://openai.bothub.chat/v1"
apiKey = "YOUR_BOTHUB_API_KEY"
)
func do(method, url string, body any) map[string]any {
var r io.Reader
if body != nil {
b, _ := json.Marshal(body)
r = bytes.NewReader(b)
}
req, _ := http.NewRequest(method, url, r)
req.Header.Set("Authorization", "Bearer "+apiKey)
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
raw, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 300 {
panic(fmt.Sprintf("%s\n%s", resp.Status, raw))
}
var out map[string]any
if err := json.Unmarshal(raw, &out); err != nil {
panic(err)
}
return out
}
func main() {
created := do("POST", baseURL+"/videos/generations", map[string]any{
"model": "veo-3.1",
"prompt": "A time-lapse of a blooming flower in a sunny garden",
"notify": false,
"settings": map[string]any{
"duration_seconds": 6,
"aspect_ratio": "16:9",
},
})
id := created["id"].(string)
fmt.Println("Request created, id:", id)
for {
time.Sleep(10 * time.Second)
video := do("GET", baseURL+"/videos/"+id+"?include_usage=true", nil)
status := video["status"].(string)
fmt.Println("Status:", status)
if status == "completed" {
fmt.Println("Video URL:", video["unsigned_urls"].([]any)[0])
return
}
if status == "failed" {
panic(video["error"])
}
}
}
# Step 1: Create the generation request
curl https://openai.bothub.chat/v1/videos/generations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_BOTHUB_API_KEY" \
-d '{
"model": "veo-3.1",
"prompt": "A time-lapse of a blooming flower in a sunny garden",
"notify": false,
"settings": {
"duration_seconds": 6,
"aspect_ratio": "16:9"
}
}'
# Step 2: Poll for status (replace VIDEO_ID with the id from step 1)
curl "https://openai.bothub.chat/v1/videos/VIDEO_ID?include_usage=true" \
-H "Authorization: Bearer YOUR_BOTHUB_API_KEY"
Response — Step 1 (creation):
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"modelId": "veo-3.1",
"providerId": "replicate",
"status": "pending",
"createdAt": "2025-01-15T10:30:00Z",
"userId": "user-id",
"error": null,
"payment": false,
"notify": false,
"unsigned_urls": []
}
Response — Step 2 (completed):
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"modelId": "veo-3.1",
"providerId": "replicate",
"status": "completed",
"createdAt": "2025-01-15T10:30:00Z",
"userId": "user-id",
"error": null,
"payment": true,
"notify": false,
"unsigned_urls": [
"https://storage.bothub.chat/videos/a1b2c3d4-e5f6-7890-abcd-ef1234567890.mp4"
],
"outputFileId": "file-uuid",
"usage": {
"caps": 250
}
}
Image-to-Video
Pass imageUrl to animate a still image. The model will use it as the first frame of the video.
- JS
- Python
- Go
- cURL
async function imageToVideo() {
const BASE_URL = 'https://openai.bothub.chat/v1';
const API_KEY = 'YOUR_BOTHUB_API_KEY';
const createRes = await fetch(`${BASE_URL}/videos/generations`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify({
model: 'kling-v2.6',
prompt: 'The person slowly turns and smiles at the camera',
imageUrl: 'https://example.com/portrait.jpg',
notify: false,
settings: {
duration_seconds: 5,
aspect_ratio: '9:16',
negative_prompt: 'blurry, low quality',
},
}),
});
const { id } = await createRes.json();
// Poll for result
let video;
while (true) {
await new Promise((r) => setTimeout(r, 10000));
const res = await fetch(`${BASE_URL}/videos/${id}`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
video = await res.json();
if (video.status === 'completed' || video.status === 'failed') break;
}
console.log(video.status === 'completed' ? video.unsignedUrls[0] : video.error);
}
imageToVideo();
import time
import requests
BASE_URL = 'https://openai.bothub.chat/v1'
API_KEY = 'YOUR_BOTHUB_API_KEY'
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {API_KEY}',
}
response = requests.post(
f'{BASE_URL}/videos/generations',
json={
'model': 'kling-v2.6',
'prompt': 'The person slowly turns and smiles at the camera',
'imageUrl': 'https://example.com/portrait.jpg',
'notify': False,
'settings': {
'duration_seconds': 5,
'aspect_ratio': '9:16',
'negative_prompt': 'blurry, low quality',
},
},
headers=headers,
)
video_id = response.json()['id']
while True:
time.sleep(10)
video = requests.get(f'{BASE_URL}/videos/{video_id}', headers=headers).json()
if video['status'] in ('completed', 'failed'):
break
print(video['unsigned_urls'][0] if video['status'] == 'completed' else video.get('error'))
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
const (
baseURL = "https://openai.bothub.chat/v1"
apiKey = "YOUR_BOTHUB_API_KEY"
)
func do(method, url string, body any) map[string]any {
var r io.Reader
if body != nil {
b, _ := json.Marshal(body)
r = bytes.NewReader(b)
}
req, _ := http.NewRequest(method, url, r)
req.Header.Set("Authorization", "Bearer "+apiKey)
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
raw, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 300 {
panic(fmt.Sprintf("%s\n%s", resp.Status, raw))
}
var out map[string]any
if err := json.Unmarshal(raw, &out); err != nil {
panic(err)
}
return out
}
func main() {
created := do("POST", baseURL+"/videos/generations", map[string]any{
"model": "kling-v2.6",
"prompt": "The person slowly turns and smiles at the camera",
"imageUrl": "https://example.com/portrait.jpg",
"notify": false,
"settings": map[string]any{
"duration_seconds": 5,
"aspect_ratio": "9:16",
"negative_prompt": "blurry, low quality",
},
})
id := created["id"].(string)
for {
time.Sleep(10 * time.Second)
video := do("GET", baseURL+"/videos/"+id, nil)
status := video["status"].(string)
if status == "completed" {
fmt.Println(video["unsigned_urls"].([]any)[0])
return
}
if status == "failed" {
fmt.Println(video["error"])
return
}
}
}
curl https://openai.bothub.chat/v1/videos/generations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_BOTHUB_API_KEY" \
-d '{
"model": "kling-v2.6",
"prompt": "The person slowly turns and smiles at the camera",
"imageUrl": "https://example.com/portrait.jpg",
"notify": false,
"settings": {
"duration_seconds": 5,
"aspect_ratio": "9:16",
"negative_prompt": "blurry, low quality"
}
}'
List Available Models
- JS
- Python
- Go
- cURL
const res = await fetch('https://openai.bothub.chat/v1/videos/models', {
headers: { Authorization: 'Bearer YOUR_BOTHUB_API_KEY' },
});
const { data } = await res.json();
console.log(data.map((m) => m.id));
import requests
res = requests.get(
'https://openai.bothub.chat/v1/videos/models',
headers={'Authorization': 'Bearer YOUR_BOTHUB_API_KEY'},
)
models = res.json()['data']
print([m['id'] for m in models])
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://openai.bothub.chat/v1/videos/models", nil)
req.Header.Set("Authorization", "Bearer YOUR_BOTHUB_API_KEY")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
raw, _ := io.ReadAll(resp.Body)
var result struct {
Data []struct {
ID string `json:"id"`
} `json:"data"`
}
if err := json.Unmarshal(raw, &result); err != nil {
panic(err)
}
for _, m := range result.Data {
fmt.Println(m.ID)
}
}
curl https://openai.bothub.chat/v1/videos/models \
-H "Authorization: Bearer YOUR_BOTHUB_API_KEY"
Example Response:
{
"object": "list",
"data": [
{ "id": "veo-3.1", "object": "model", "created": 1705334400, "owned_by": "replicate" },
{ "id": "kling-v2.6", "object": "model", "created": 1705334400, "owned_by": "replicate" },
{ "id": "gen4_turbo", "object": "model", "created": 1705334400, "owned_by": "runway" }
]
}
Status Reference
| Status | Description |
|---|---|
pending | Generation is in progress |
completed | Video is ready — see unsigned_urls |
failed | Generation failed — see error for details |
Tips
- Polling interval: Poll every 10–15 seconds. Most models complete within 1–3 minutes.
- Email notifications: Set
notify: trueto receive an email when generation finishes — useful for long-running jobs. - Duration limits: Available values depend on the model. When in doubt, use
5or6seconds. - Image-to-video: The
imageUrlmust be a publicly accessible URL. Supported formats: JPEG, PNG, WebP.