A zsh function: submit a video prompt, keep your terminal free
A 19-line .zshrc function sends a prompt to Sume Wan 3.0 (2 s, 480p, $0.125), returns at once and saves the MP4 later. Do not name a variable status.

Most shell recipes for video APIs block the terminal until the file arrives, and a clip can take from 30 seconds to several minutes. If you work in zsh, you can do better with one function in ~/.zshrc. video "steam rising from a coffee cup" submits the job, prints the polling URL and gives you the prompt back. The wait happens in the background, and a line appears when the file is saved.
The function uses Wan 3.0 at 2 seconds and 480p, which Sume prices at $0.0625 a second, so $0.125 a run. It needs curl and jq, and a SUME_API_KEY in the environment.
The function
Source the file and call video "prompt" out.mp4, with the file name optional. The submit step runs in the foreground so a bad key or a 402 shows up straight away. The poll loop sits inside a brace group ended with &!, which backgrounds the group and disowns it, so the shell prints no job-control lines. I ran it against a local stand-in server with a short delay: the prompt came back first, and the saved line and file followed.
video() {
local base=${SUME_API_BASE:-https://api.sume.com} out=${2:-clip-$(date +%H%M%S).mp4}
local -a auth=(-H "Authorization: Bearer ${SUME_API_KEY:?set SUME_API_KEY}")
local poll
poll=$(curl -sS -X POST $base/v1/videos $auth -H 'Content-Type: application/json' \
-d "$(jq -nc --arg p "$1" '{model:"wan-3.0",prompt:$p,duration:2,resolution:"480p"}')" \
| jq -er .polling_url) || { print -u2 "submit failed"; return 1 }
print "submitted: $poll"
{
local job state
while sleep ${POLL_SECONDS:-30}; do
job=$(curl -sS $poll $auth); state=$(jq -r .status <<<$job)
[[ $state == (failed|cancelled) ]] && { print -u2 "video: $state"; return 1 }
[[ $state == completed ]] && break
done
curl -sSL $(jq -r '.unsigned_urls[0]' <<<$job) $auth -o $out
print "video: saved $out, cost $(jq .usage.cost <<<$job)"
} &!
}The zsh trap
My first draft named the poll variable status, the obvious name for a job's status field, and the background block died with read-only variable: status. In zsh, status is a special parameter that holds the exit code of the last command, the same role $? has, so assigning to it fails. Bash has no such rule, which is why a script that works in bash can break when it is pasted into a zsh function. The function calls it state.
Two smaller zsh points. auth is an array, so $auth expands to the two words of the header option without the splitting quirks of bash. And [[ $state == (failed|cancelled) ]] is a pattern match, so no regex is needed.
What the job returns
The submit response is a 202 with id, polling_url, status and model. The function polls that URL as given, treats failed and cancelled as the end, and on completed follows unsigned_urls[0], the content URL, with curl -L. Finished jobs report usage.cost, the Sume billable amount, which the function prints.
If you want to retry safely after a flaky connection, add an Idempotency-Key header to the submit call; Sume's guide says a replay with the same key returns the original job. Without it, running video twice with the same prompt starts two jobs and charges for two.
Sources
Related posts
More in Developers
- 503 provider_capacity_exceeded on a transcription submit: retry?
provider_capacity_exceeded means Sume's dispatch queue is full. Retry later with the same idempotency key. It differs from 429 queue_full and 402.
- A/B test two AI video models by API with a stable hash bucket
Split production video jobs between two Sume model ids by hashing a job key, so retries keep the same arm. Python code, a duration table and a 1,000-key check.
- Agent Completions: each call is a new thread, owned by your user
Every Sume Agent Completion starts a fresh thread; you cannot continue one with thread_id or send assistant turns, and runs are user-owned, not team-owned.
- Sume agent run webhook: one event per turn, not per clip
A Sume agent run sends one agent.run.terminal webhook per agent turn, never one per generated artifact. Key your handler on the run id.
Written by Sume