Bash script to submit, poll, and download a Sume video
A 22-line bash and jq script for POST /v1/videos: idempotency key, 30-second polls, a 20-minute cap, and exit codes 0 to 3 that a scheduler can read.

A shell script can run the whole Sume video flow if it does four things: send an Idempotency-Key, poll the job's polling URL, stop on a terminal status, and return an exit code that tells a scheduler what happened. The script below does that with curl and jq in 22 lines. It exits 0 when the file is saved, 1 for a failed job, 2 for a canceled one, and 3 when its own deadline runs out.
The deadline is the part people forget. A client-side timeout does not cancel a Sume job. The job keeps running and keeps billing, so a script that gives up silently leaves paid work behind. This one prints the polling URL when it gives up, so you can read the job later.
The script
Run it as SUME_API_KEY=... bash video.sh order-8823-mug-v1. The argument becomes the idempotency key, so running the same command twice does not create a second job. The loop runs 40 times at 30 seconds, which is a 20-minute deadline.
#!/usr/bin/env bash
set -euo pipefail
API=https://api.sume.com
AUTH="Authorization: Bearer ${SUME_API_KEY:?set SUME_API_KEY}"
KEY="${1:?usage: video.sh <idempotency-key>}"
JOB=$(curl -fsS "$API/v1/videos" -H "$AUTH" \
-H "Content-Type: application/json" -H "Idempotency-Key: $KEY" \
-d '{"model":"seedance-2.5","prompt":"Slow push-in on a ceramic mug","duration":5}')
URL=$(jq -r .polling_url <<<"$JOB")
for _ in $(seq 1 40); do
sleep 30
S=$(curl -fsS "$URL" -H "$AUTH")
case "$(jq -r .status <<<"$S")" in
completed)
curl -fsS "$(jq -r '.unsigned_urls[0]' <<<"$S")" -H "$AUTH" -o out.mp4
exit 0 ;;
failed) jq -c .error <<<"$S" >&2; exit 1 ;;
cancelled) echo "job cancelled" >&2; exit 2 ;;
esac
done
echo "deadline: job still running at $URL" >&2
exit 3Exit codes
The /v1/videos route spells the canceled status with two l's, cancelled. The generic /v1/jobs routes use canceled. If you switch routes, change the case label.
| Exit code | Meaning | Sume status |
|---|---|---|
| 0 | MP4 saved to out.mp4 | completed |
| 1 | Error object printed to stderr | failed |
| 2 | Job canceled | cancelled |
| 3 | Deadline reached, job still running | pending or in_progress |
Why curl -f and set -e
curl -f turns an HTTP error status into a non-zero exit, and set -euo pipefail stops the script on it. A 429 on submit then stops the script instead of trying to parse an error body as a job. If you want to retry, wrap the submit in a function that reads the retry-after header, and keep the same key.
The ${SUME_API_KEY:?...} form stops the script with a message if the key is missing, which is better than sending an empty bearer token and reading a 401.
Where this stops being enough
This script holds one job in one process. For a batch, the plan decides how many jobs are accepted at a time: Free accepts 6, Pro 24, Startup 48, and Scale 120, counting processing and queued together. Past that, a submit returns 429 queue_full. Use webhooks or a small worker with a job table once you move past a handful of jobs, and keep the polling URL stored next to each job id.
Running it from a scheduler
Cron, a CI job, and a Makefile target all read exit codes the same way. Map 0 to success, treat 1 and 2 as final, and treat 3 as a signal to look again rather than to resubmit. Because the idempotency key is the script argument, rerunning the same command after a 3 does not create a second job; it returns the original job and keeps polling.
Keep the key meaningful. A key built from an order number and a version, such as the one in the example, makes a retry safe and makes a deliberate change obvious, since a new version string is a new job. A key reused with a different body returns 409 idempotency_conflict, which is the right signal that the request changed.
Log the polling URL and the final JSON to a file. When a job fails, the request id in the error object is the fastest thing to quote to support.
Sources
Related posts
More in Developers
- Proxy for a Sume video button: forward the click's Idempotency-Key
The docs proxy sample adds crypto.randomUUID() on every forwarded call, so a browser retry makes a second paid job. Take the key from the client for /v1/videos.
- Budget 250 product shots: read each Sume model's price in code
A short Python script reads the pricing line from Sume's per-model endpoints route and prints the cost of 250 product shots. Reference rules and a worked table.
- Build IMAGE_REF tags in Python for up to 10 Omni references
A Python helper turns a list of up to 10 image URLs into an Omni Flash prompt with matching IMAGE_REF tags, 0-based, in list order. Runs as written.
- Bulk run item completed is not success: check before you publish
A Sume Format bulk child can be completed without being a success. Read each result and use per-child webhooks; no queue webhook exists.
Written by Sume