BullMQ delayed job that polls an AI video job and reschedules itself
A BullMQ worker reads Sume's job status once, then adds the next poll with a delay from next_poll_after_seconds, so no worker slot is held while a clip renders.

In BullMQ, poll an AI video render with a delayed job that re-adds itself, not with a setTimeout inside the processor. The processor reads Sume's GET /v1/jobs/{id}/status once, and if terminal is false it calls queue.add('poll', data, { delay }) with the number of seconds from next_poll_after_seconds (Sume jobs guide, read 2026-10-06). Worker concurrency then limits how many reads run at once, not how many videos you can have in flight.
The example polls a Wan 3.0 clip. At 720p, Sume's Wan 3.0 rate is $0.125 per second, so a 10 second clip is $1.25.
What does the worker look like?
Run it as an ES module with Node 20 or later and SUME_API_KEY set. The attempts counter caps the loop at 120 reads.
import { Queue, Worker } from 'bullmq';
const connection = { host: '127.0.0.1', port: 6379 };
const queue = new Queue('sume-poll', { connection });
const headers = { Authorization: `Bearer ${process.env.SUME_API_KEY}` };
export const track = (jobId) =>
queue.add('poll', { jobId, n: 0 }, { delay: 15_000, removeOnComplete: true });
new Worker('sume-poll', async ({ data }) => {
const res = await fetch(
`https://api.sume.com/v1/jobs/${data.jobId}/status`, { headers });
if (!res.ok) throw new Error(`status ${res.status}`); // BullMQ retries
const s = await res.json();
if (s.terminal) {
console.log(data.jobId, s.sume_status);
return;
}
if (data.n < 120) {
const wait = (s.next_poll_after_seconds ?? 15) * 1000;
await queue.add('poll', { ...data, n: data.n + 1 },
{ delay: wait, removeOnComplete: true });
}
}, { connection, concurrency: 5 });Which status fields should the processor trust?
| Field | Use in the processor |
|---|---|
| terminal | Stop re-adding the poll job when true |
| sume_status | completed, failed or canceled; store it |
| result_ready | True when the output can be fetched |
| next_poll_after_seconds | Delay for the next poll; absent on a terminal job |
Does a retry of the poller bill a second clip?
No, as long as the poller only reads. Keep the paid POST /v1/videos in a separate step with an Idempotency-Key you own, and let the poller use GET /v1/jobs/{id}/status. A client-side give-up never cancels the render; Sume's jobs guide says the job keeps running, so store the job id and read it again later instead of submitting a second time.
Sources
Related posts
More in Developers
- A calendar file for AI model shutdown dates: .ics from Python
Generate an .ics file with all-day events and 14-day reminders for the gpt-image-1 and gpt-image-1.5 shutdown dates, then import it into any calendar app.
- Cancel a queued transcription job: what the 409 means
POST /v1/jobs/{id}/cancel works only before work starts. After that it returns 409 job_generation_already_started and the job finishes and bills normally.
- Celery task for an AI video API: submit, poll, retry on Wan 3.0
Two Celery tasks for Sume's /v1/videos: one submits a Wan 3.0 job with an Idempotency-Key, one polls with self.retry(countdown) and stops on a terminal status.
- Check a Shorts timeline body offline: a Python mirror of the rules
Catch Timeline 1.0 refusals you can see in the body (start at 0, 0.5 s coverage, transitions, even sizes) in 28 lines of Python before the plan call.
Written by Sume