Generate videos, use reference images or videos, poll jobs, and download completed files.
Video Generation
Use POST /videos/generation to start a video generation job. Video generation is asynchronous:
the trigger request returns a jobId, then you poll GET /jobs/{id} until the job has completed.
For the shared polling pattern, see Jobs.
Request shape
{
"modelID": "seedance-2.0",
"prompt": "<PROMPT>"
}Required fields:
| Field | Description |
|---|---|
modelID | Public video model ID configured in PhotoGPT. |
prompt | Text prompt describing the video to generate. |
Common optional fields:
| Field | Description |
|---|---|
numVideos | Number of videos to generate. |
referenceImages | Image URLs with roles such as first frame, last frame, or reference image. |
referenceVideos | Video URLs with reference_video role. |
elementIDs | Existing elements/assets to include when supported by the selected model. |
options.veo | Veo-specific options. |
options.seedance | Seedance-specific options. |
options.kling | Kling-specific options. |
options.grok | Grok-specific options. |
Send only the option object for the model selected by modelID.
Video model guide
Choose a model based on the kind of motion control, reference input, duration, and resolution you need.
| Model family | Model IDs | Option group |
|---|---|---|
| Veo | veo-3.1, veo-3.1-fast | options.veo |
| Seedance | seedance-1.5-pro, seedance-2.0 | options.seedance |
| Kling video | kling-video-standard, kling-video-pro | options.kling |
| Kling motion | kling-motion-standard, kling-motion-pro | options.kling |
| Grok Imagine | grok-imagine-1.5 | options.grok |
Veo 3.1
Use modelID: "veo-3.1" for high-quality text-to-video or image-to-video jobs.
| Setting | Values or behavior |
|---|---|
| Option group | options.veo |
aspectRatio | 16:9, 9:16. Defaults to 16:9. |
duration | 4, 6, 8. Defaults to 8; must be 8 when using reference_image. |
resolution | 720p, 1080p. Defaults to 720p. |
enhancePrompt | Boolean. Defaults to true. |
generateAudio | Boolean. Defaults to false. |
negativePrompt | Text describing what to avoid. |
seed | Integer for repeatable outputs. |
Veo 3.1 Fast
Use modelID: "veo-3.1-fast" when you want the Veo option surface with faster turnaround.
| Setting | Values or behavior |
|---|---|
| Option group | options.veo |
aspectRatio | 16:9, 9:16. Defaults to 16:9. |
duration | 4, 6, 8. Defaults to 8; must be 8 when using reference_image. |
resolution | 720p, 1080p. Defaults to 720p. |
enhancePrompt | Boolean. Defaults to true. |
generateAudio | Boolean. Defaults to false. |
negativePrompt | Text describing what to avoid. |
seed | Integer for repeatable outputs. |
Seedance 1.5 Pro
Use modelID: "seedance-1.5-pro" for Seedance generation when you need draft mode or automatic
duration.
| Setting | Values or behavior |
|---|---|
| Option group | options.seedance |
resolution | 480p, 720p, 1080p. Draft mode forces 480p. |
ratio | 16:9, 4:3, 1:1, 3:4, 9:16, 21:9, adaptive. |
duration | -1 for auto, or 4 to 12. |
draftMode | Boolean. Only supported by seedance-1.5-pro; forces 480p. |
generateAudio | Boolean. |
returnLastFrame | Boolean. |
seed | Integer for repeatable outputs. |
reference_image inputs are dropped by validation for seedance-1.5-pro.
Seedance 2.0
Use modelID: "seedance-2.0" for Seedance jobs that need image or video references.
| Setting | Values or behavior |
|---|---|
| Option group | options.seedance |
resolution | 480p, 720p, 1080p. |
ratio | 16:9, 4:3, 1:1, 3:4, 9:16, 21:9, adaptive. |
duration | 4 to 15. |
referenceImages | Up to 9 reference_image inputs. |
referenceVideos | Up to 3 reference_video inputs. |
generateAudio | Boolean. |
returnLastFrame | Boolean. |
seed | Integer for repeatable outputs. |
Kling Video Standard
Use modelID: "kling-video-standard" for standard Kling text-to-video or image-to-video jobs.
| Setting | Values or behavior |
|---|---|
| Option group | options.kling |
duration | 3 to 15. Defaults to 10. |
aspectRatio | 16:9, 9:16, 1:1. Text-to-video only; ignored for image-to-video or elements. |
generateAudio | Boolean. Defaults to false. |
elementIDs | Supported where available. Kling accepts at most 4 total element/reference items. |
Kling Video Pro
Use modelID: "kling-video-pro" when you want the higher-capability Kling video model.
| Setting | Values or behavior |
|---|---|
| Option group | options.kling |
duration | 3 to 15. Defaults to 10. |
aspectRatio | 16:9, 9:16, 1:1. Text-to-video only; ignored for image-to-video or elements. |
generateAudio | Boolean. Defaults to false. |
elementIDs | Supported where available. Kling accepts at most 4 total element/reference items. |
Kling Motion Standard
Use modelID: "kling-motion-standard" for motion-control workflows from existing visual elements or
references.
| Setting | Values or behavior |
|---|---|
| Option group | options.kling |
characterOrientation | image, video. Defaults to video. |
keepOriginalSound | Boolean. Defaults to true. |
elementIDs | At most 4 total items after combining elements, reference images, and videos. |
Kling motion-control models do not accept duration, aspectRatio, or generateAudio.
Kling Motion Pro
Use modelID: "kling-motion-pro" when you need the higher-capability Kling motion-control model.
| Setting | Values or behavior |
|---|---|
| Option group | options.kling |
characterOrientation | image, video. Defaults to video. |
keepOriginalSound | Boolean. Defaults to true. |
elementIDs | At most 4 total items after combining elements, reference images, and videos. |
Kling motion-control models do not accept duration, aspectRatio, or generateAudio.
Grok Imagine 1.5
Use modelID: "grok-imagine-1.5" for Grok video generation.
| Setting | Values or behavior |
|---|---|
| Option group | options.grok |
duration | 1 to 15. Defaults to 8. |
aspectRatio | 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3. Defaults to 16:9. |
resolution | 480p, 720p. Defaults to 480p. |
Text-to-video
const API_KEY = '<YOUR_API_KEY>'
const BASE_URL = 'https://developer.photogptai.com/api'
const headers = {
Authorization: `Bearer ${API_KEY}`,
'API-Version': '1',
}
const payload = {
modelID: 'veo-3.1',
prompt: 'cinematic close-up of a skincare bottle rotating on wet stone',
numVideos: 1,
options: {
veo: {
duration: 8,
aspectRatio: '16:9',
resolution: '1080p',
generateAudio: false,
enhancePrompt: true,
negativePrompt: 'blur, low quality, distorted text',
},
},
}
const response = await fetch(`${BASE_URL}/videos/generation`, {
method: 'POST',
headers: {
...headers,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
if (!response.ok) {
throw new Error(await response.text())
}
const body = await response.json()
const jobId = body.result.jobId
console.log('Video generation queued:', jobId)import requests
API_KEY = "<YOUR_API_KEY>"
BASE_URL = "https://developer.photogptai.com/api"
headers = {
"Authorization": f"Bearer {API_KEY}",
"API-Version": "1",
}
payload = {
"modelID": "veo-3.1",
"prompt": "cinematic close-up of a skincare bottle rotating on wet stone",
"numVideos": 1,
"options": {
"veo": {
"duration": 8,
"aspectRatio": "16:9",
"resolution": "1080p",
"generateAudio": False,
"enhancePrompt": True,
"negativePrompt": "blur, low quality, distorted text",
},
},
}
response = requests.post(f"{BASE_URL}/videos/generation", headers=headers, json=payload)
response.raise_for_status()
job_id = response.json()["result"]["jobId"]
print("Video generation queued:", job_id)Image-to-video
Use referenceImages when the selected model supports image-to-video or first-frame control.
Do not send backend-only preparation metadata such as containsHuman; the backend handles it.
Supported image roles:
| Role | Description |
|---|---|
first_frame | Use the image as the opening frame. |
last_frame | Use the image as the ending frame. |
reference_image | Use the image as visual guidance. |
const payload = {
modelID: 'seedance-2.0',
prompt: 'slow push-in camera movement, soft studio lighting',
numVideos: 1,
referenceImages: [
{
url: 'https://example.com/product-frame.png',
role: 'first_frame',
},
],
options: {
seedance: {
duration: 6,
ratio: '16:9',
resolution: '1080p',
generateAudio: false,
returnLastFrame: true,
},
},
}
const response = await fetch(`${BASE_URL}/videos/generation`, {
method: 'POST',
headers: {
...headers,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
if (!response.ok) {
throw new Error(await response.text())
}
const body = await response.json()
const jobId = body.result.jobIdpayload = {
"modelID": "seedance-2.0",
"prompt": "slow push-in camera movement, soft studio lighting",
"numVideos": 1,
"referenceImages": [
{
"url": "https://example.com/product-frame.png",
"role": "first_frame",
},
],
"options": {
"seedance": {
"duration": 6,
"ratio": "16:9",
"resolution": "1080p",
"generateAudio": False,
"returnLastFrame": True,
},
},
}
response = requests.post(f"{BASE_URL}/videos/generation", headers=headers, json=payload)
response.raise_for_status()
job_id = response.json()["result"]["jobId"]Reference video
Use referenceVideos when the selected model accepts a video as motion or style reference.
Do not send backend-only preparation metadata such as duration; the backend computes it when
needed.
const payload = {
modelID: 'seedance-2.0',
prompt: 'match the reference camera motion with a new product subject',
numVideos: 1,
referenceVideos: [
{
url: 'https://example.com/reference-motion.mp4',
role: 'reference_video',
},
],
options: {
seedance: {
duration: 8,
ratio: '16:9',
resolution: '720p',
generateAudio: false,
},
},
}
const response = await fetch(`${BASE_URL}/videos/generation`, {
method: 'POST',
headers: {
...headers,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
if (!response.ok) {
throw new Error(await response.text())
}
const body = await response.json()
const jobId = body.result.jobIdpayload = {
"modelID": "seedance-2.0",
"prompt": "match the reference camera motion with a new product subject",
"numVideos": 1,
"referenceVideos": [
{
"url": "https://example.com/reference-motion.mp4",
"role": "reference_video",
},
],
"options": {
"seedance": {
"duration": 8,
"ratio": "16:9",
"resolution": "720p",
"generateAudio": False,
},
},
}
response = requests.post(f"{BASE_URL}/videos/generation", headers=headers, json=payload)
response.raise_for_status()
job_id = response.json()["result"]["jobId"]Poll the job
Poll GET /jobs/{id} until the job reaches a terminal status. The helper below follows the
same pattern described in Jobs.
async function waitForJob(jobId) {
const runningStatuses = new Set(['created', 'queued', 'running'])
while (true) {
const response = await fetch(`${BASE_URL}/jobs/${jobId}`, {
headers,
})
if (!response.ok) {
throw new Error(await response.text())
}
const body = await response.json()
const job = body.result
const status = String(job.status ?? '').toLowerCase()
if (!runningStatuses.has(status)) {
return job
}
await new Promise((resolve) => setTimeout(resolve, 8000))
}
}
const job = await waitForJob(jobId)
const status = String(job.status ?? '').toLowerCase()
if (status === 'success') {
for (const video of job.videos ?? []) {
console.log(video.id, video.url)
}
} else {
throw new Error(job.error ?? `Video generation failed: ${job.status}`)
}import time
def wait_for_job(job_id: str):
running_statuses = {"created", "queued", "running"}
while True:
response = requests.get(f"{BASE_URL}/jobs/{job_id}", headers=headers)
response.raise_for_status()
job = response.json()["result"]
status = job.get("status", "").lower()
if status not in running_statuses:
return job
time.sleep(8)
job = wait_for_job(job_id)
if job.get("status", "").lower() == "success":
for video in job.get("videos", []):
print(video["id"], video["url"])
else:
raise RuntimeError(job.get("error") or f"Video generation failed: {job.get('status')}")List and inspect videos
Use GET /videos to list completed videos.
const params = new URLSearchParams({
videoType: 'all',
pageNum: '1',
pageSize: '20',
})
const response = await fetch(`${BASE_URL}/videos?${params}`, {
headers,
})
if (!response.ok) {
throw new Error(await response.text())
}
const body = await response.json()
const videos = body.resultresponse = requests.get(
f"{BASE_URL}/videos",
headers=headers,
params={"videoType": "all", "pageNum": 1, "pageSize": 20},
)
response.raise_for_status()
videos = response.json()["result"]Use GET /videos/{videoID} to inspect one video.
const videoId = '<VIDEO_ID>'
const response = await fetch(`${BASE_URL}/videos/${videoId}`, {
headers,
})
if (!response.ok) {
throw new Error(await response.text())
}
const body = await response.json()
const video = body.resultvideo_id = "<VIDEO_ID>"
response = requests.get(f"{BASE_URL}/videos/{video_id}", headers=headers)
response.raise_for_status()
video = response.json()["result"]Download a video
Video downloads are prepared in two steps.
First, request the download:
const videoId = '<VIDEO_ID>'
const response = await fetch(`${BASE_URL}/videos/${videoId}/create-download`, {
headers,
})
if (!response.ok) {
throw new Error(await response.text())
}video_id = "<VIDEO_ID>"
response = requests.get(f"{BASE_URL}/videos/{video_id}/create-download", headers=headers)
response.raise_for_status()Then poll the download endpoint until the status is ready. The current download status values
are inprogress, ready, and error.
while (true) {
const response = await fetch(`${BASE_URL}/videos/${videoId}/download`, {
headers,
})
if (!response.ok) {
throw new Error(await response.text())
}
const body = await response.json()
const download = body.result
if (download.status === 'ready') {
console.log(download.url)
break
}
if (download.status === 'error') {
throw new Error('Video download preparation failed.')
}
console.log('Preparing download:', download.percentComplete ?? 0)
await new Promise((resolve) => setTimeout(resolve, 5000))
}import time
while True:
response = requests.get(f"{BASE_URL}/videos/{video_id}/download", headers=headers)
response.raise_for_status()
download = response.json()["result"]
if download.get("status") == "ready":
print(download["url"])
break
if download.get("status") == "error":
raise RuntimeError("Video download preparation failed.")
print("Preparing download:", download.get("percentComplete", 0))
time.sleep(5)