waitForJob times out at 20 minutes: why it does not fit a Vercel route
Sume SDK waitForJob waits 20 minutes by default and the job keeps billing if it throws. Vercel functions default to 300 s, so wait in a worker.

waitForJob in @sume-com/sdk 0.2.0 waits up to 20 minutes before it throws SumeJobTimeoutError, which is far longer than a default Vercel function. The Vercel duration page (last updated 2026-08-24) gives 300 seconds as the default on every plan. Call waitForJob from a long-lived worker, not from a request handler, and let a route only submit and return.
Defaults side by side
The helper polls every 2 seconds at minimum, and when next_poll_after_seconds in the status payload is longer, the server value wins. waitForRun waits 10 minutes and subscribeFormatRun 20 minutes by default; waitForRun needs family.
| Item | Default | Maximum |
|---|---|---|
| Vercel function (Hobby) | 300 s | 300 s |
| Vercel function (Pro, Enterprise) | 300 s | 800 s; 1,800 s beta |
waitForJob | 20 min | Set with timeout |
waitForRun | 10 min | Set with timeout |
subscribeFormatRun | 20 min | Set with timeout |
Sume sync wait | Up to 30 s | 30 s |
A timeout does not cancel the job
The helper throws and the job continues to run, and it still bills. Both errors, SumeJobTimeoutError and SumeJobRequestError, carry the jobId, so store it and read the job again later. Do not submit a second job; if you must retry a submit, send the same Idempotency-Key.
import { createSumeClient, waitForJob, SumeJobTimeoutError } from "@sume-com/sdk";
const client = createSumeClient({ apiKey: process.env.SUME_API_KEY! });
export async function wait(jobId: string, signal?: AbortSignal) {
try {
return await waitForJob(jobId, { client, timeout: 10 * 60_000, signal });
} catch (err) {
if (err instanceof SumeJobTimeoutError) {
return { jobId, status: "still_running" as const };
}
throw err;
}
}Where to put the wait
Pick one of three shapes and keep the route short.
- Webhook: submit with
mode: "webhook", return, and verify the signed event. - Worker: a queue consumer or a Railway or Modal service runs
waitForJob. - Browser polling: the route reads one status per request and returns
next_poll_after_seconds.
Passing an abort signal
waitForJob accepts a signal that aborts both the wait and the request in flight. Wire it to your platform shutdown so a deploy does not leave a loop running. Aborting does not cancel the job; it only stops your reading. To cancel, use the cancel URL on the job envelope, which works only before generation starts.
The onStatus callback runs on every read, including the last one, with the status and a snapshot that has next_action. Use it to update a progress row in your own database, so a restart can resume from the stored job id.
Formats are different
subscribeFormatRun creates a Format run and waits, with the same 20 minute default, and it generates an idempotencyKey for you when you do not pass one. Pass your own when the run belongs to an order, so a retry cannot start a second run. There is no SSE stream in this SDK version, so every wait is a poll.
Sources
Related posts
More in Developers
- Sume wave_size_hint and a Worker subrequest limit: submit in waves
A Worker fan-out of Sume jobs hits 50 subrequests on Free. Size each wave from generation_limits, not from the hint alone, and stop at queue_capacity_remaining.
- Sume webhook retries: 10 attempts, 30 s apart, 10 s timeout each
The delivery schedule for Sume job webhooks: 10 attempts, fixed 30 s spacing, 10 s timeout, about 4.5 minutes of retries, then redeliver and the status poll.
- Swift: URLSession async/await for one 30-second Wan 3.0 job
A 28-line main.swift that submits wan-3.0 for 30 seconds, polls with Task.sleep and saves the MP4. Runs on macOS or Linux with swiftc.
- Switch video models by changing one string: what can still break
On Sume's /v1/videos you swap the model id and keep the body. Duration range, resolution and aspect ratio are the three fields that may need adjusting.
Written by Sume