PhotoGPT Developers

Poll asynchronous generation jobs and track model training readiness.

Generation Jobs

Generation requests are asynchronous. The trigger request returns quickly, then the backend continues processing the job.

The common pattern is:

  1. Start work with POST /images/generation, POST /images/upscaling, or POST /videos/generation.
  2. Save the returned jobId.
  3. Poll GET /jobs/{id} until the status leaves the running state.
  4. Read generated images, generated videos, or error from the job payload.

Training note

The OpenAPI schema documents GET /models/{modelID}/train as returning a success string, not a jobId. For training, track readiness by polling the model with GET /models/{modelID} until status becomes ready. Training usually takes 20 to 25 minutes, so poll about every 5 minutes instead of every second.

Job response

GET /jobs/{id} returns a result object with job metadata and any completed outputs.

FieldDescription
jobIdJob identifier returned by the trigger request.
statusCurrent backend state.
imagesGenerated image records when the job produced images.
videosGenerated video records when the job produced videos.
errorFailure reason when the job did not complete.
modelIdRelated model ID when the job is model-scoped.
createdAtJob creation timestamp.

Poll a job

Use a short delay between polling attempts. The current job status values are created, queued, running, success, and failed.

const RUNNING_STATUSES = new Set(['created', 'queued', 'running'])
const SUCCESS_STATUS = 'success'

async function waitForJob(jobId, intervalMs = 5000) {
  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 (!RUNNING_STATUSES.has(status)) {
      return job
    }

    await new Promise((resolve) => setTimeout(resolve, intervalMs))
  }
}

const job = await waitForJob('<JOB_ID>')
const status = String(job.status ?? '').toLowerCase()

if (status !== SUCCESS_STATUS) {
  throw new Error(job.error ?? `Job failed: ${job.status}`)
}
import time

RUNNING_STATUSES = {"created", "queued", "running"}
SUCCESS_STATUS = "success"


def wait_for_job(job_id: str, interval_seconds: int = 5):
    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(interval_seconds)


job = wait_for_job("<JOB_ID>")

if job.get("status", "").lower() != SUCCESS_STATUS:
    raise RuntimeError(job.get("error") or f"Job failed: {job.get('status')}")

Read outputs

Image generation and upscaling jobs return image records in images.

for (const image of job.images ?? []) {
  console.log(image.id, image.url)
}
for image in job.get("images", []):
    print(image["id"], image["url"])

Video generation jobs return video records in videos.

for (const video of job.videos ?? []) {
  console.log(video.id, video.url)
}
for video in job.get("videos", []):
    print(video["id"], video["url"])

API reference

On this page