Job Polling

Direct generation and publishing operations follow a create-then-poll pattern.


How it works

  1. Submit a request, such as POST /image/generate or POST /publish/instagram.
  2. Poll GET /jobs/inference/{inferenceJobId} or GET /jobs/publish/{outputJobId} until the job reaches a terminal status.
  3. Read output media or publish details from the completed job response.
GET/api/v1/jobs/inference/{inferenceJobId}
GET/api/v1/jobs/publish/{outputJobId}

Local CLI editing does not create a public API job. The wonda edit commands render locally and return a mediaId or downloaded file path when the render completes.

Status values

StatusTerminalDescription
idleNoJob is queued and waiting to start.
lockedNoJob has been claimed by a worker.
in_progressNoJob is actively running.
queuedNoPublish job is waiting in queue.
succeededYesJob completed successfully.
failedYesJob encountered an error.
canceledYesInference job was canceled.

Polling strategy

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

Chaining pattern

A typical workflow chains async API work with local CLI editing:

upload -> generate -> poll -> take output mediaId -> edit locally with wonda CLI -> publish -> poll

Full flow example

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

async function pollJob(type, id) {
  let delay = 1000;
  while (true) {
    const job = await fetch(`${BASE}/jobs/${type}/${id}`, {
      headers: { Authorization: HEADERS.Authorization },
    }).then((response) => response.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((resolve) => setTimeout(resolve, delay));
    delay = Math.min(delay * 1.5, 5000);
  }
}

const generation = 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((response) => response.json());

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

const publish = await fetch(`${BASE}/publish/instagram`, {
  method: "POST",
  headers: HEADERS,
  body: JSON.stringify({
    mediaId: generated.mediaId,
    instagramAccountId: "550e8400-e29b-41d4-a716-446655440000",
    caption: "Made with Wonda",
    product: "IMAGE",
  }),
}).then((response) => response.json());

const publishResult = await pollJob("publish", publish.outputJobId);
(() => {})("Publish status:", publishResult.status);