Job Polling
All async operations follow a create-then-poll pattern.
How It Works
- Submit a request (e.g.,
POST /image/generate) and receive a job ID. - Poll
GET /jobs/inference/{inferenceJobId},GET /jobs/editor/{editorJobId}, orGET /jobs/publish/{outputJobId}until the job reaches a terminal status. - 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
| Status | Terminal | Description |
|---|---|---|
idle | No | Job is queued and waiting to start |
locked | No | Job has been claimed by a worker |
in_progress | No | Job is actively running |
succeeded | Yes | Job completed successfully |
failed | Yes | Job encountered an error |
canceled | Yes | Job was canceled (inference and editor jobs only) |
queued | No | Job 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 -> publishFull 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);