PhotoGPT Developers

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:

FieldDescription
modelIDPublic video model ID configured in PhotoGPT.
promptText prompt describing the video to generate.

Common optional fields:

FieldDescription
numVideosNumber of videos to generate.
referenceImagesImage URLs with roles such as first frame, last frame, or reference image.
referenceVideosVideo URLs with reference_video role.
elementIDsExisting elements/assets to include when supported by the selected model.
options.veoVeo-specific options.
options.seedanceSeedance-specific options.
options.klingKling-specific options.
options.grokGrok-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 familyModel IDsOption group
Veoveo-3.1, veo-3.1-fastoptions.veo
Seedanceseedance-1.5-pro, seedance-2.0options.seedance
Kling videokling-video-standard, kling-video-prooptions.kling
Kling motionkling-motion-standard, kling-motion-prooptions.kling
Grok Imaginegrok-imagine-1.5options.grok

Veo 3.1

Use modelID: "veo-3.1" for high-quality text-to-video or image-to-video jobs.

SettingValues or behavior
Option groupoptions.veo
aspectRatio16:9, 9:16. Defaults to 16:9.
duration4, 6, 8. Defaults to 8; must be 8 when using reference_image.
resolution720p, 1080p. Defaults to 720p.
enhancePromptBoolean. Defaults to true.
generateAudioBoolean. Defaults to false.
negativePromptText describing what to avoid.
seedInteger for repeatable outputs.

Veo 3.1 Fast

Use modelID: "veo-3.1-fast" when you want the Veo option surface with faster turnaround.

SettingValues or behavior
Option groupoptions.veo
aspectRatio16:9, 9:16. Defaults to 16:9.
duration4, 6, 8. Defaults to 8; must be 8 when using reference_image.
resolution720p, 1080p. Defaults to 720p.
enhancePromptBoolean. Defaults to true.
generateAudioBoolean. Defaults to false.
negativePromptText describing what to avoid.
seedInteger for repeatable outputs.

Seedance 1.5 Pro

Use modelID: "seedance-1.5-pro" for Seedance generation when you need draft mode or automatic duration.

SettingValues or behavior
Option groupoptions.seedance
resolution480p, 720p, 1080p. Draft mode forces 480p.
ratio16:9, 4:3, 1:1, 3:4, 9:16, 21:9, adaptive.
duration-1 for auto, or 4 to 12.
draftModeBoolean. Only supported by seedance-1.5-pro; forces 480p.
generateAudioBoolean.
returnLastFrameBoolean.
seedInteger 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.

SettingValues or behavior
Option groupoptions.seedance
resolution480p, 720p, 1080p.
ratio16:9, 4:3, 1:1, 3:4, 9:16, 21:9, adaptive.
duration4 to 15.
referenceImagesUp to 9 reference_image inputs.
referenceVideosUp to 3 reference_video inputs.
generateAudioBoolean.
returnLastFrameBoolean.
seedInteger for repeatable outputs.

Kling Video Standard

Use modelID: "kling-video-standard" for standard Kling text-to-video or image-to-video jobs.

SettingValues or behavior
Option groupoptions.kling
duration3 to 15. Defaults to 10.
aspectRatio16:9, 9:16, 1:1. Text-to-video only; ignored for image-to-video or elements.
generateAudioBoolean. Defaults to false.
elementIDsSupported 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.

SettingValues or behavior
Option groupoptions.kling
duration3 to 15. Defaults to 10.
aspectRatio16:9, 9:16, 1:1. Text-to-video only; ignored for image-to-video or elements.
generateAudioBoolean. Defaults to false.
elementIDsSupported 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.

SettingValues or behavior
Option groupoptions.kling
characterOrientationimage, video. Defaults to video.
keepOriginalSoundBoolean. Defaults to true.
elementIDsAt 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.

SettingValues or behavior
Option groupoptions.kling
characterOrientationimage, video. Defaults to video.
keepOriginalSoundBoolean. Defaults to true.
elementIDsAt 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.

SettingValues or behavior
Option groupoptions.grok
duration1 to 15. Defaults to 8.
aspectRatio1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3. Defaults to 16:9.
resolution480p, 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:

RoleDescription
first_frameUse the image as the opening frame.
last_frameUse the image as the ending frame.
reference_imageUse 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.jobId
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,
        },
    },
}

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.jobId
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,
        },
    },
}

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.result
response = 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.result
video_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)

API reference

On this page