Poll Sume status in a loop, read the full job once at the end
Poll the lightweight status for progress and read GET /v1/jobs/{id} once at a terminal state to get the error. What next_action tells you and the 409 on result.

Poll GET /v1/jobs/{id} until the response says terminal, then act on next_action: fetch the result when it is fetch_result, and inspect the job record when it is inspect_events or the job failed. The result endpoint will not give you an error, because it answers 409 job_not_completed for failed and canceled jobs.
So a loop that goes straight from "done polling" to /result breaks on every failure.
What the status response tells you
Besides the job state, the response carries guidance fields.
| Field | Use |
|---|---|
| terminal | Stop polling when true |
| result_ready | The result can be fetched |
| next_action | poll_status, fetch_result or inspect_events |
| next_poll_after_seconds | Sleep this long; null when terminal |
| cancelable and cancel_url | Whether and where to cancel |
Where the error is
After a failure, read the job record once and log the error body, which uses the {error: {code, message, request_id, details}} shape. Keep the request id from the error envelope for support; it is also in the response headers.
The video content route also answers 409 job_failed after a terminal failure, so the same rule applies there.
The loop in words
Read status. If not terminal, sleep for next_poll_after_seconds, or two seconds if it is missing. If terminal and result_ready, fetch the result. Otherwise read the record, record the error and stop. Put a deadline around the whole thing; docs suggest about 20 minutes for video.
Think of the loop as having three outputs: a result to use, an error to record, or a timeout from your own budget. Each should end in a stored state that you can query later, such as done, failed with the error code, or timed_out with the job id still attached. The last one is the important one: a timed_out job may still complete and bill, so keep the id, poll it occasionally, and cancel it only if it has not started generating.
Tradeoffs
One extra read at the end is cheap compared with guessing why a job ended. The cost is a second code path that you must test, which is why a fake local server is worth the effort.
The same pattern applies to webhooks. A job.failed event tells you something failed; the job record tells you why. Treat both as the same final read, whichever path got you there, so you have one function that records a terminal outcome and one place to fix it.
Sources
Related posts
More in Developers
- Poll many transcription jobs without a 429: next_poll_after_seconds
Poll Sume job status with the next_poll_after_seconds the API sends, back off on 429 with retry-after, and stop on a terminal status.
- Clicks or gaps when joining TTS MP3 clips: render WAV, join once
Joined MP3 voiceover clips can gap or click. Sume's Timeline audio docs explain why: MP3 adds priming padding at each edge. Keep WAV until the last step.
- Postgres SKIP LOCKED poll table for AI video jobs with next_poll_at
Track Sume video jobs in one Postgres table with next_poll_at, claim due rows with FOR UPDATE SKIP LOCKED and reschedule from next_poll_after_seconds.
- Pre-flight check for Shorts and TikTok ad files: an ffprobe script
A 30-line Python script reads a rendered MP4 with ffprobe and flags it if it is not vertical, over 3 minutes, under 540x960, over 500 MB or under 516 kbps.
Written by Sume