Video generation API job states: map remote snapshots to product behavior
Video generation API job states are the contract between a background render and your product. Genace currently uses queued, running, succeeded, failed, and canceled. Keeping these values intact in your application makes incident diagnosis easier than compressing everything into a single loading boolean.
The HTTP layer and the job layer describe different things. A job GET returning HTTP 200 means that the snapshot was retrieved successfully. Its state can still be failed. Conversely, an HTTP 503 while reading a running job does not prove that the job changed state at all.
Build an explicit state table
| Remote state | Product behavior | Collection action |
|---|---|---|
| queued | Show accepted or waiting | Continue collecting the same job |
| running | Show rendering | Continue collecting; progress may be absent |
| succeeded | Show preparing download until storage finishes | Validate and copy returned outputs |
| failed | Show a render failure | Save the structured error and reconcile credits |
| canceled | Show cancellation | Stop collection and reconcile the saved result |
The first two states are active. The final three are terminal. Do not assume the visible sequence always moves neatly from queued to running: an adapter may report an upstream queue snapshot after the submission service initially marked the local row running. Terminal-state handling should be stable even if active snapshots change order.
Separate rendering from delivery
A succeeded job is not necessarily a file safely stored in your system. Maintain an additional application delivery status such as not_started, copying, available, or copy_failed. If the download fails, retry the download rather than generating another video.
function classify(job) {
switch (job.state) {
case 'queued':
case 'running':
return { action: 'wait', id: job.id };
case 'succeeded':
return { action: 'copy', outputs: job.outputs ?? [] };
case 'failed':
return { action: 'report', error: job.error ?? null };
case 'canceled':
return { action: 'stop' };
default:
return { action: 'investigate', state: job.state };
}
}
The default branch is intentional defensive behavior. An unexpected future state should generate a diagnostic and preserve the snapshot, rather than being treated as success or silently discarded. The example is application logic, not an official SDK.
Keep accounting fields attached
The serializer exposes credits_held and nullable credits_actual. The submission service deducts credits before it submits upstream. Its terminal update path records the held amount as actual consumption for success and zero for failure or cancellation after the refund path runs. Retain these returned values for reconciliation instead of inferring a balance from a UI label.
Common mistakes include treating null outputs as an empty successful result, using a timer to change running into failed, and retrying canceled jobs automatically. Those shortcuts confuse remote outcomes with local preferences and can spend additional credits without a fresh decision.
Verification checklist
Run the classifier over one fixture per state, an unknown state, and a succeeded fixture with null outputs. Verify that no active state produces a download and no failed state becomes a generic network exception. Confirm that file-copy retries remain attached to the original job.
Continue with failed job responses, output storage, and cost budgeting.
Continue with production operations
Where these facts come from
- codebase: src/ai/jobs/service.ts
- codebase: src/ai/jobs/http.ts
- Editorial keyword hypothesis; search volume not measured