Laravel queued job for an AI video API: delay, redispatch, poll
A Laravel ShouldQueue job reads a Sume video job once and redispatches itself with ->delay() from next_poll_after_seconds, so no worker sleeps during a render.

In Laravel, wait for an AI video by dispatching a small queued job that reads Sume's status once and, if the job is not terminal, dispatches itself again with ->delay(...). Laravel documents delay() on dispatch as the way to keep a job from being available immediately (Laravel queues docs, read 2026-10-06), and Sume's status response tells you how long to wait with next_poll_after_seconds (Sume jobs guide, read 2026-10-06).
The job below uses Laravel's Http client and a services.sume.key config value. It stops after 120 reads, the same cap a 30 minute wall clock gives at 15 second spacing.
What is the job class?
Re-dispatching a fresh job, rather than release(), keeps the attempt counter for real errors separate from the number of polls. Laravel's note about Amazon SQS limiting delays to 15 minutes does not matter here because Sume caps a queued job's suggested wait at 30 seconds.
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Facades\Http;
class PollSumeVideo implements ShouldQueue
{
use Dispatchable, Queueable;
public $tries = 3;
public function __construct(public string $jobId, public int $left = 120) {}
public function handle(): void
{
$s = Http::withToken(config('services.sume.key'))
->get("https://api.sume.com/v1/jobs/{$this->jobId}/status")
->throw()->json();
if ($s['terminal'] ?? false) {
// update your videos row from $s['sume_status']
} elseif ($this->left > 0) {
self::dispatch($this->jobId, $this->left - 1)
->delay(now()->addSeconds($s['next_poll_after_seconds'] ?? 15));
}
}
}Where is the paid request?
Not in this class. Submit from a controller or another job with Http::withToken(...)->withHeaders(['Idempotency-Key' => $video->id])->post('https://api.sume.com/v1/videos', [...]), store the returned id on the model, then PollSumeVideo::dispatch($id)->delay(now()->addSeconds(15)). Because the poll job never pays, $tries = 3 is safe: a Laravel retry re-reads and cannot create a second clip.
| sume_status | terminal | What to store |
|---|---|---|
| completed | true | Fetch the result, copy the file, keep the job id |
| failed | true | Show the public error, no output exists; Sume refunds the hold |
| canceled | true | The job stopped before generation started |
| queued or processing | false | Redispatch with the suggested delay |
What if the job sits in queued for a long time?
queued is a normal accepted state. Sume applies the workspace's concurrency limit when workers move jobs to processing, so a burst of submits can wait in line without any error. The generation admission guide lists the limits: a Free workspace runs 1 job at a time with room for 5 more queued, Pro runs 4 with 20 queued. Keep polling at the suggested delay; do not resubmit.
Sources
Related posts
More in Developers
- Let QA override the image model per request, with an allowlist
An internal render endpoint that honors an X-Image-Model header only for allowlisted Sume ids, so QA can test gpt-image-2.5 before the config flips. Python.
- List your transcription jobs: GET /v1/jobs by type, status, cursor
GET /v1/jobs?type=speech_to_text pages 100 jobs at a time, newest first. Join on each row's idempotency_key, not array position, to rebuild a batch.
- Live webinar captions: Sume has no streaming STT, so use chunks
MAI-Transcribe-2-Streaming is $0.54 an hour. Sume STT is $0.60, async and capped at 10 minutes a job. How to caption a webinar recording in chunks.
- Log Sume ratelimit-remaining on every Python urllib response
A urllib handler that logs ratelimit-remaining and ratelimit-reset on each Sume response, warning under 10 percent. It also sees 429 replies.
Written by Sume