Jobs

Monitor and retrieve results from image generation jobs.

GET/v1/jobs/{job_id}

Get the status and results of a generation job.

Headers

Authorization: Bearer YOUR_API_KEY

Path Parameters

ParameterTypeDescription
job_idstringThe job ID returned from the generate endpoint.

Example Request

curl "https://api.productai.photo/v1/jobs/job_abc123xyz" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response - Pending/Processing

{
  "job_id": "job_abc123xyz",
  "status": "processing",
  "progress": 45,
  "created_at": "2024-01-15T10:30:00Z",
  "started_at": "2024-01-15T10:30:05Z",
  "model": "nanobanana2",
  "num_images": 2
}

Response - Completed

{
  "job_id": "job_abc123xyz",
  "status": "completed",
  "progress": 100,
  "created_at": "2024-01-15T10:30:00Z",
  "started_at": "2024-01-15T10:30:05Z",
  "completed_at": "2024-01-15T10:30:35Z",
  "model": "nanobanana2",
  "num_images": 2,
  "images": [
    {
      "url": "https://cdn.productai.photo/generated/img_001.jpg",
      "width": 1024,
      "height": 1024
    },
    {
      "url": "https://cdn.productai.photo/generated/img_002.jpg",
      "width": 1024,
      "height": 1024
    }
  ],
  "tokens_used": 10
}

Response - Failed

{
  "job_id": "job_abc123xyz",
  "status": "failed",
  "created_at": "2024-01-15T10:30:00Z",
  "started_at": "2024-01-15T10:30:05Z",
  "failed_at": "2024-01-15T10:30:15Z",
  "error": {
    "code": "generation_failed",
    "message": "Unable to process the input image"
  }
}

Response Fields

FieldTypeDescription
job_idstringUnique job identifier.
statusstringOne of: pending, processing, completed, failed
progressintegerProgress percentage (0-100).
imagesarrayGenerated images (only present when completed).
images[].urlstringCDN URL of the generated image.
tokens_usedintegerNumber of credits consumed.
errorobjectError details (only present when failed).
GET/v1/jobs

List all jobs for the authenticated user.

Query Parameters

ParameterTypeDefaultDescription
limitinteger20Number of jobs to return. Max: 100.
offsetinteger0Number of jobs to skip.
statusstringallFilter by status.

Example Request

curl "https://api.productai.photo/v1/jobs?limit=10&status=completed" \
  -H "Authorization: Bearer YOUR_API_KEY"

Success Response (200 OK)

{
  "jobs": [
    {
      "job_id": "job_abc123xyz",
      "status": "completed",
      "model": "nanobanana2",
      "num_images": 2,
      "created_at": "2024-01-15T10:30:00Z",
      "completed_at": "2024-01-15T10:30:35Z"
    }
  ],
  "total": 156,
  "limit": 10,
  "offset": 0,
  "has_more": true
}

Polling Pattern

Poll the job status endpoint until the job completes. Recommended polling interval: 2-5 seconds.

Python

import requests
import time

def wait_for_job(job_id, api_key, timeout=120):
    url = f"https://api.productai.photo/v1/jobs/{job_id}"
    headers = {"Authorization": f"Bearer {api_key}"}
    
    start = time.time()
    while time.time() - start < timeout:
        response = requests.get(url, headers=headers)
        job = response.json()
        
        if job["status"] == "completed":
            return job["images"]
        elif job["status"] == "failed":
            raise Exception(job["error"]["message"])
        
        time.sleep(3)
    
    raise TimeoutError("Job did not complete in time")

# Usage
images = wait_for_job("job_abc123xyz", "YOUR_API_KEY")
for img in images:
    print(img["url"])

JavaScript

async function waitForJob(jobId, apiKey, timeout = 120000) {
  const url = `https://api.productai.photo/v1/jobs/${jobId}`;
  const start = Date.now();
  
  while (Date.now() - start < timeout) {
    const response = await fetch(url, {
      headers: { "Authorization": `Bearer ${apiKey}` }
    });
    const job = await response.json();
    
    if (job.status === "completed") return job.images;
    if (job.status === "failed") throw new Error(job.error.message);
    
    await new Promise(r => setTimeout(r, 3000));
  }
  throw new Error("Job timed out");
}

// Usage
const images = await waitForJob("job_abc123xyz", "YOUR_API_KEY");
images.forEach(img => console.log(img.url));

Webhooks

Instead of polling, set a webhook URL on the API Access page. We'll POST the result of every job to that endpoint as it completes. Your endpoint should return 200 to acknowledge receipt.

Webhook Payload

POST https://your-server.com/webhook
Content-Type: application/json

{
  "status": "success",
  "image_url": "https://cdn.productai.photo/generated/img_001.jpg",
  "job_id": "287344"
}

status is success or error. On error, image_url is omitted.

Static source IPs

All webhook requests originate from two static addresses. If your firewall restricts inbound traffic, allowlist both — deliveries are load balanced across the pair, so any individual request may come from either one.

54.88.136.216
54.84.188.199

Verifying your endpoint

When you save a webhook URL we immediately send a test request from the same addresses, so it doubles as a check that your allowlist is correct.

POST https://your-server.com/webhook
User-Agent: ProductAI-Webhook-Validator/1.0

{
  "test": true,
  "message": "Webhook validation test from ProductAI"
}

Related