wait_timeout_seconds 30 is not a 30-second video

The 30 in wait_timeout_seconds is how long your HTTP request may block, not how long a clip may run. A 30-second video job still needs a poll or webhook.

4 min readSume
All posts

The 30 in wait_timeout_seconds has nothing to do with clip length. It is a clamp of 0..30 on how long your submit request may block in sync or subscribe mode. A 30-second video is a different 30: the output duration you ask the model for. Sending mode: "sync" for a 30-second Seedance 2.5 clip will most likely return a job id and sync.timed_out: true, and you then poll that job.

Two limits that share a number

The docs name the confusion directly: 30 seconds is a wait budget, not a job duration. Video, avatar-video and face-swap jobs usually do not finish inside it, and Sume recommends async or webhook for any work that can last longer than 30 seconds, which includes most video.

The two thirties (read 2026-10-05)
wait_timeout_secondsduration
What it limitsHow long the submit HTTP request blocksHow many seconds of video the model makes
Where it livesmode: "sync" / "subscribe" communication fieldsModel field, for example seedance-2.5 accepts 4 to 30
What happens at the limitResponse is still 2xx with a job id; poll nextThe request is invalid if outside the model's range
Does it change the priceNoYes, billing follows output seconds

What a timed-out sync response means

A 2xx still carries the job id: the end of the wait budget is not an admission failure. The envelope has status_url, result_url, events_url, cancel_url and a sync object. sync.timed_out is true when the wait returned before a terminal state, and sync.capacity_exhausted is true when Sume skipped the wait because its waiter budget was full. Then follow next_poll_after_seconds if it is present.

You must not submit a new paid job for the same intent. If you want to retry the submit itself, send the same Idempotency-Key again so the retry returns the original job and does not bill a second one.

What to use instead

Submit with async, store the job id, and poll with backoff. The docs' client-side equivalent of subscribe() is a loop in your own code that can last minutes, because no HTTP request stays open. waitForJob in the TypeScript SDK is that loop. For a push, use mode: "webhook" and keep the status poll as a backup.

One more trap: subscribe is not a stream. It runs the same bounded waiter as sync, and there is no SSE or WebSocket transport on the Developer API today. If you want progress, submit async and read GET /v1/jobs/:id/events, which is a pull snapshot.

What to set instead

Set duration on the generate request, within the range the model's catalog entry lists. Set wait_timeout_seconds only if you want the HTTP call to hold for a terminal state, and understand that a 30-second render will almost never finish inside a 30-second wait once queueing and processing are counted.

The safe default is async: you get a 202 with the envelope and poll URLs at once, then poll status_url until terminal is true. If you use sync or subscribe, a non-terminal response is not a failure. The docs say to poll and not to resubmit, because a resubmit without the same key creates a second job and a second charge.

The mode changes how you learn the outcome. It never changes whether Sume creates a job, what the job costs or how long it takes.

  • duration sets the clip length.
  • wait_timeout_seconds is clamped to 0..30.
  • Non-terminal after the wait: poll, do not resubmit.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume