PhotoGPT Developers

Generate images with trained models, popular public models, and reference images.

Image Generation

Use POST /images/generation to start image generation jobs. Generation is asynchronous: the trigger request returns a jobId, then you poll GET /jobs/{id} until the job has finished.

For the shared polling pattern, see Jobs.

Image generation requests send the selected model fields directly:

{
  "modelID": "gpt-image-2",
  "prompt": "<PROMPT>"
}

Use POST /images/generation for both trained models and popular public image models. Use POST /images/upscaling when you want to upscale an existing image.

Model choices

Model choiceUse whenRequired fields
Trained model IDGenerate identity-consistent images from your model.modelID. Most workflows also include prompt, preset, or inputImageId.
Popular models like NanoBanana Pro, GPT Image 2, SeedreamGenerate, edit, or recompose images with public IDs.modelID, prompt. You can also include preset.
Existing imageCreate a higher-resolution version of an image asset.imageID with POST /images/upscaling.

Generate with a trained model

Use a trained model ID from GET /models when you need identity consistency. Wait until the model status is ready before starting generation.

Common fields:

FieldDescription
modelIDTrained model ID returned by GET /models.
promptText prompt for the generated image.
presetPreset tag returned by GET /presets.
inputImageIdUploaded reference image ID for trained-model reference workflows.
widthOutput width in pixels.
heightOutput height in pixels.
numImagesNumber of images to generate.
stepsGeneration step count.
seedSeed for repeatable outputs.
runTypeOptional run mode.
enhanceFaceEnable face enhancement for portrait workflows.
styleStrengthControls how strongly a reference image influences the result.
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: '<MODEL_ID>',
  prompt: 'professional studio headshot, charcoal blazer, soft key light',
  width: 1024,
  height: 1024,
  numImages: 1,
  steps: 30,
  seed: 0,
  enhanceFace: true,
}

const response = await fetch(`${BASE_URL}/images/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('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": "<MODEL_ID>",
    "prompt": "professional studio headshot, charcoal blazer, soft key light",
    "width": 1024,
    "height": 1024,
    "numImages": 1,
    "steps": 30,
    "seed": 0,
    "enhanceFace": True,
}

response = requests.post(f"{BASE_URL}/images/generation", headers=headers, json=payload)
response.raise_for_status()

job_id = response.json()["result"]["jobId"]
print("Generation queued:", job_id)

Use an uploaded reference image

For trained-model reference workflows, upload the reference image first and pass the returned image ID as inputImageId.

Use type: "auxiliaryType" for reference images. Use type: "modelInput" for model training images.

const referenceFile = document.querySelector('input[type="file"]').files?.[0]

if (!referenceFile) {
  throw new Error('Select a reference image before uploading.')
}

const uploadData = {
  modelID: '<MODEL_ID>',
  type: 'auxiliaryType',
}

const formData = new FormData()
formData.append('file', referenceFile)
formData.append('data', JSON.stringify(uploadData))

const uploadResponse = await fetch(`${BASE_URL}/images/upload`, {
  method: 'POST',
  headers,
  body: formData,
})

if (!uploadResponse.ok) {
  throw new Error(await uploadResponse.text())
}

const uploadedImage = await uploadResponse.json()
const inputImageId = uploadedImage.result.id

const payload = {
  modelID: '<MODEL_ID>',
  prompt: 'cinematic portrait, warm window light',
  inputImageId,
  styleStrength: 0.35,
  numImages: 1,
  width: 1024,
  height: 1024,
}

const response = await fetch(`${BASE_URL}/images/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
import json
import requests

upload_data = {
    "modelID": "<MODEL_ID>",
    "type": "auxiliaryType",
}

with open("reference.jpg", "rb") as image_file:
    files = {
        "file": image_file,
        "data": (None, json.dumps(upload_data), "application/json"),
    }
    upload_response = requests.post(
        f"{BASE_URL}/images/upload",
        headers=headers,
        files=files,
    )

upload_response.raise_for_status()
input_image_id = upload_response.json()["result"]["id"]

payload = {
    "modelID": "<MODEL_ID>",
    "prompt": "cinematic portrait, warm window light",
    "inputImageId": input_image_id,
    "styleStrength": 0.35,
    "numImages": 1,
    "width": 1024,
    "height": 1024,
}

response = requests.post(f"{BASE_URL}/images/generation", headers=headers, json=payload)
response.raise_for_status()

job_id = response.json()["result"]["jobId"]

Use a public model ID when you want high-quality general image generation, image editing, product shots, scene recomposition, or reference-image workflows without training a model first. Popular choices include NanoBanana Pro, GPT Image 2, and Seedream.

These models use the same POST /images/generation endpoint as trained models, but the input rules are different. The request body is matched against the model-family schema selected by modelID.

FieldSupported values or behavior
modelIDnanobanana, nanobanana-pro, nanobanana-2, seedream, seedream-4_5, seedream-5-lite, gpt-image-2.
promptRequired. Describe the image, edit, style, subject, and any constraints.
presetOptional preset tag returned by GET /presets. The preset will be applied to the image.
aspectRatio1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9. Defaults to 1:1.
numImages1 to 4.
referenceImageURLsPublic image URLs. Limits are model-specific and listed below.
optionsSend only the option group for the selected model: gemini, seedream, or openai. Do not mix option groups.

NanoBanana

Use modelID: "nanobanana" for Gemini-backed image generation and editing.

SettingValues
Option groupoptions.gemini
Reference image limitUp to 3 URLs in referenceImageURLs.
imageSize1K, 2K, 4K. Defaults to 1K.
safetyFilterLevelOFF, BLOCK_NONE, BLOCK_ONLY_HIGH, BLOCK_MEDIUM_AND_ABOVE, BLOCK_LOW_AND_ABOVE.
sequentialModedisabled, sequential. Use sequential when multiple images should run one after another.

NanoBanana Pro

Use modelID: "nanobanana-pro" when you want the higher-capability NanoBanana model for more detailed prompts, sharper image edits, or larger reference sets.

SettingValues
Option groupoptions.gemini
Reference image limitUp to 14 URLs in referenceImageURLs.
imageSize1K, 2K, 4K. Defaults to 1K.
safetyFilterLevelOFF, BLOCK_NONE, BLOCK_ONLY_HIGH, BLOCK_MEDIUM_AND_ABOVE, BLOCK_LOW_AND_ABOVE.
sequentialModedisabled, sequential. Use sequential when multiple images should run one after another.

NanoBanana 2

Use modelID: "nanobanana-2" for the newer NanoBanana family model when you need the same Gemini option surface with support for larger reference sets.

SettingValues
Option groupoptions.gemini
Reference image limitUp to 14 URLs in referenceImageURLs.
imageSize1K, 2K, 4K. Defaults to 1K.
safetyFilterLevelOFF, BLOCK_NONE, BLOCK_ONLY_HIGH, BLOCK_MEDIUM_AND_ABOVE, BLOCK_LOW_AND_ABOVE.
sequentialModedisabled, sequential. Use sequential when multiple images should run one after another.

Seedream

Use modelID: "seedream" for Seedream image generation with the widest Seedream image-size range and fast optimization mode.

SettingValues
Option groupoptions.seedream
Reference image limitUp to 10 URLs in referenceImageURLs.
imageSize1K, 2K, 4K.
optimizeModestandard, fast.
sequentialModeauto, disabled. Forced to auto when numImages > 1.

Seedream 4.5

Use modelID: "seedream-4_5" for Seedream 4.5 when you want higher-resolution outputs and do not need fast optimization mode.

SettingValues
Option groupoptions.seedream
Reference image limitUp to 10 URLs in referenceImageURLs.
imageSize2K, 4K.
optimizeModestandard.
sequentialModeauto, disabled. Forced to auto when numImages > 1.

Seedream 5 Lite

Use modelID: "seedream-5-lite" for lighter Seedream generation with 2K and 3K output sizes.

SettingValues
Option groupoptions.seedream
Reference image limitUp to 10 URLs in referenceImageURLs.
imageSize2K, 3K.
optimizeModestandard.
sequentialModeauto, disabled. Forced to auto when numImages > 1.

GPT Image 2

Use modelID: "gpt-image-2" for OpenAI image generation and editing. It is a strong default for product compositions, marketing assets, and prompt-following edits from reference images.

SettingValues or behavior
Option groupoptions.openai
Reference image limitUp to 10 URLs in referenceImageURLs.
imageSize1K, 2K, 4K. Combined with aspectRatio to resolve exact dimensions.
qualityauto, high, medium, low.
backgroundauto, opaque. Defaults to auto.
moderationauto, low. Defaults to auto.
outputFormatpng, jpeg. Defaults to jpeg.
outputCompression0 to 100. Only valid when outputFormat is jpeg.

Resolved GPT Image 2 dimensions are constrained by the API to a maximum edge of 3840px, a maximum 3:1 aspect ratio, and dimensions that resolve to edge multiples of 16.

const payload = {
  modelID: 'gpt-image-2',
  prompt: 'minimal product photo of a matte black water bottle on marble',
  preset: 'editorial',
  aspectRatio: '1:1',
  numImages: 1,
  referenceImageURLs: ['https://example.com/reference-product.png'],
  options: {
    openai: {
      imageSize: '1K',
      quality: 'high',
      outputFormat: 'jpeg',
    },
  },
}

const response = await fetch(`${BASE_URL}/images/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": "gpt-image-2",
    "prompt": "minimal product photo of a matte black water bottle on marble",
    "preset": "editorial",
    "aspectRatio": "1:1",
    "numImages": 1,
    "referenceImageURLs": [
        "https://example.com/reference-product.png",
    ],
    "options": {
        "openai": {
            "imageSize": "1K",
            "quality": "high",
            "outputFormat": "jpeg",
        },
    },
}

response = requests.post(f"{BASE_URL}/images/generation", headers=headers, json=payload)
response.raise_for_status()

job_id = response.json()["result"]["jobId"]

Poll the job

Poll GET /jobs/{id} with the jobId returned by POST /images/generation. The helper below is 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, 5000))
  }
}

const job = await waitForJob(jobId)
const status = String(job.status ?? '').toLowerCase()

if (status === 'success') {
  for (const image of job.images ?? []) {
    console.log(image.id, image.url)
  }
} else {
  throw new Error(job.error ?? `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(5)


job = wait_for_job(job_id)

if job.get("status", "").lower() == "success":
    for image in job.get("images", []):
        print(image["id"], image["url"])
else:
    raise RuntimeError(job.get("error") or f"Generation failed: {job.get('status')}")

Fetch generated images

After the job completes, generated images are returned in result.images. You can also inspect image metadata later:

const imageId = '<IMAGE_ID>'

const response = await fetch(`${BASE_URL}/images/${imageId}`, {
  headers,
})

if (!response.ok) {
  throw new Error(await response.text())
}

const body = await response.json()
const image = body.result
console.log(image.url)
image_id = "<IMAGE_ID>"

response = requests.get(f"{BASE_URL}/images/{image_id}", headers=headers)
response.raise_for_status()

image = response.json()["result"]
print(image["url"])

To list output images for a model:

const params = new URLSearchParams({
  imageType: 'output',
  pageNum: '1',
  pageSize: '20',
})

const response = await fetch(`${BASE_URL}/models/<MODEL_ID>/images?${params}`, {
  headers,
})

if (!response.ok) {
  throw new Error(await response.text())
}

const body = await response.json()
const images = body.result
response = requests.get(
    f"{BASE_URL}/models/<MODEL_ID>/images",
    headers=headers,
    params={"imageType": "output", "pageNum": 1, "pageSize": 20},
)
response.raise_for_status()

images = response.json()["result"]

API reference

On this page