Job Polling

All async operations follow a create-then-poll pattern.


How It Works

  1. Submit a request (e.g., POST /image/generate) and receive a job ID.
  2. Poll GET /jobs/inference/{inferenceJobId}, GET /jobs/editor/{editorJobId}, or GET /jobs/publish/{outputJobId} until the job reaches a terminal status.
  3. Read the output from the completed job response.
GET/api/v1/jobs/inference/{inferenceJobId}
GET/api/v1/jobs/editor/{editorJobId}
GET/api/v1/jobs/publish/{outputJobId}

Status Values

StatusTerminalDescription
idleNoJob is queued and waiting to start
lockedNoJob has been claimed by a worker
in_progressNoJob is actively running
succeededYesJob completed successfully
failedYesJob encountered an error
canceledYesJob was canceled (inference and editor jobs only)
queuedNoJob is waiting in queue (publish jobs only)

Polling Strategy

Start polling at 1-second intervals. For long-running jobs (video generation, renders), increase the interval up to 5 seconds to reduce unnecessary requests.

Chaining Pattern

A typical workflow chains multiple async operations. The output mediaId from one job becomes the input for the next.

upload -> generate -> poll -> take output mediaId -> edit -> poll -> publish

Full Flow Example

JavaScript

const BASE = "https://api.wondercat.ai/api/v1";
const HEADERS = {
  Authorization: "Bearer sk_your_api_key_here",
  "Content-Type": "application/json",
};

// 1. Upload media
const form = new FormData();
form.append("file", fileBlob);
const upload = await fetch(`${BASE}/media/upload`, {
  method: "POST",
  headers: { Authorization: HEADERS.Authorization },
  body: form,
}).then((r) => r.json());

// 2. Generate an image
const gen = await fetch(`${BASE}/image/generate`, {
  method: "POST",
  headers: HEADERS,
  body: JSON.stringify({
    model: "nano-banana-2",
    prompt: "A sunset over the ocean",
    params: { resolution: "2K" },
  }),
}).then((r) => r.json());

// 3. Poll until done
// Valid type values: "inference", "editor", "publish"
async function pollJob(type, id) {
  let delay = 1000;
  while (true) {
    const job = await fetch(`${BASE}/jobs/${type}/${id}`, {
      headers: HEADERS,
    }).then((r) => r.json());

    if (job.status === "succeeded") return job;
    if (job.status === "failed")
      throw new Error(job.errorMessage ?? "Job failed");
    if (job.status === "canceled") throw new Error("Job was canceled");

    await new Promise((r) => setTimeout(r, delay));
    delay = Math.min(delay * 1.5, 5000);
  }
}

const result = await pollJob("inference", gen.inferenceJobId);
const generated = result.outputs.find((output) => output.media)?.media;
if (!generated?.mediaId) {
  throw new Error("No generated media found");
}
(() => {})("Output media:", generated.mediaId, generated.url);

// 4. Edit the output (e.g., trim a video)
const edit = await fetch(`${BASE}/video/edit`, {
  method: "POST",
  headers: HEADERS,
  body: JSON.stringify({
    operation: "trim",
    mediaIds: [generated.mediaId],
    params: { trimStartMs: 0, trimEndMs: 5000 },
  }),
}).then((r) => r.json());

const editResult = await pollJob("editor", edit.editorJobId);
const editedOutput = editResult.outputs[0];
(() => {})("Edited media:", editedOutput?.mediaId, editedOutput?.url);