Video generation API polling: a bounded loop that preserves the job ID
Video generation API polling should answer one question: what happened to a job that already exists? Keep creation outside the polling function. That boundary makes reconnecting, restarting, and handling transport errors much safer because none of those recovery actions can silently create another render.
Genace returns job_id from the generation POST and id from a job GET. The read endpoint returns a JSON snapshot, including state, outputs, error, and credit fields. A successful HTTP read can describe a failed render. Always check both layers.
Implement a bounded collector
This Node.js helper accepts an existing identifier and returns either a completed job or a resumable pending result. It uses an immediate status read followed by a local delay. Long polling with wait=30 is also available; choose a window that fits your deployment’s connection budget.
async function collect(jobId, maxReads = 20) {
const headers = {
Authorization: `Bearer ${process.env.GENACE_API_KEY}`,
};
for (let attempt = 0; attempt < maxReads; attempt += 1) {
const response = await fetch(
`https://genace.ai/api/v1/jobs/${encodeURIComponent(jobId)}?wait=0`,
{ headers, signal: AbortSignal.timeout(15000) }
);
if (!response.ok) {
return { kind: 'read_error', jobId, status: response.status };
}
const job = await response.json();
if (['succeeded', 'failed', 'canceled'].includes(job.state)) {
return { kind: 'terminal', job };
}
await new Promise((resolve) => setTimeout(resolve, 5000));
}
return { kind: 'pending', jobId };
}
console.log(await collect(process.env.GENACE_JOB_ID));
The helper deliberately returns a pending result when its local budget ends. It does not fabricate a timeout failure in the remote state machine. Save that result and let a later worker continue with the same identifier.
Interpret the snapshot
Queued and running mean collection should continue. Succeeded means inspect outputs before attempting a download. Failed means preserve error.code and error.message for diagnosis. Canceled is a separate terminal outcome, and should not trigger a retry merely because no file exists.
Outputs and progress are explicitly nullable in the serializer. Avoid reading outputs[0].url before checking that the array exists and contains an appropriate video item. Likewise, a null progress value means no numeric progress was supplied; it is not a measurement of zero percent.
The current GET route overrides its rate limit to sixty requests per minute and skips the active-generation concurrency check. Requests still pass through authentication and rate limiting. Multiple collectors sharing one key can exhaust that request budget together, so one poller per job is usually enough.
Avoid overlapping loops
A common UI bug starts a new interval after every render without clearing the previous interval. Another starts polling in both the browser and backend while neither has a clear responsibility. Assign one durable collector and let interfaces read its saved snapshot where possible. If multiple readers are intentional, account for their combined traffic.
Verification checklist
Exercise the collector against saved fixtures for every state, a null outputs value, an HTTP error, and a local read-budget expiration. Verify that the last case keeps the job identifier. Then perform a read-only check of a known job and inspect the stored result.
See job state handling, retry backoff, and serverless collection.
Continue with production operations
Where these facts come from
- codebase: src/app/api/v1/jobs/[id]/route.ts
- codebase: src/ai/jobs/http.ts
- codebase: src/app/api/v1/video/generations/route.ts
- Editorial keyword hypothesis; search volume not measured