curl and jq video step for CI with exit codes 0, 2 and 3

A bash script that submits to Sume POST /v1/videos, polls with jq, and exits 2 on a rejected submit and 3 on a failed render so a CI job can tell them apart.

4 min readSume
All posts

A CI video step needs three outcomes it can tell apart: success, a request Sume refused, and a render that started and failed. The bash and jq script below returns 0, 2 and 3 for those, so a pipeline can retry the third and stop on the second.

Teams that scripted the removed OpenAI Videos API (the deprecations page lists it as removed on 2026-09-24) usually had a one-line curl. The port needs a loop, because Sume's POST /v1/videos answers 202 and the work finishes later.

What each exit code means

The submit either returns a polling_url or it returns an error envelope. If jq cannot find polling_url, the script prints the body to stderr and exits 2. That covers 400 unsupported_parameter, 402 insufficient_credits, 401 and 404. None of those get better by retrying the same body.

Once polling starts, the statuses are pending, in_progress, completed, failed and cancelled. The script treats the first two as keep waiting and the third as done. Everything else prints the job's error string, a plain string on this surface, and exits 3.

Exit codes and CI action (Sume docs, read 2026-10-05)
ExitCauseCI action
0completed, file savedContinue
2submit refused, no polling_urlFix the request, do not retry
3status failed or cancelledRead the error, then retry once
othercurl or jq errorCheck network and tools

The script

It needs bash, curl and jq. Export SUME_API_KEY and optionally ROW_ID, which becomes the Idempotency-Key, so a CI retry of the same step returns the original job instead of a second billed one. The model is gemini-omni-flash-1.1 at 4 seconds, 720p and 16:9, inside its 3 to 10 second window.

#!/usr/bin/env bash
set -euo pipefail
api=https://api.sume.com/v1/videos
auth=(-H "Authorization: Bearer $SUME_API_KEY")
body='{"model":"gemini-omni-flash-1.1","prompt":"Paper boat drifting on a rain puddle","duration":4,"resolution":"720p","aspect_ratio":"16:9"}'
job=$(curl -sS --max-time 60 "${auth[@]}" -H 'Content-Type: application/json' \
  -H "Idempotency-Key: ${ROW_ID:-shell-demo-1}" -d "$body" "$api")
url=$(jq -er '.polling_url' <<<"$job") || { echo "$job" >&2; exit 2; }
while :; do
  job=$(curl -sS --max-time 30 "${auth[@]}" "$url")
  case $(jq -r .status <<<"$job") in
    pending|in_progress) sleep 10 ;;
    completed) break ;;
    *) jq -r '.error // "failed"' <<<"$job" >&2; exit 3 ;;
  esac
done
curl -sS --max-time 120 -o "${OUT:-clip.mp4}" "$(jq -r '.unsigned_urls[0]' <<<"$job")"
echo "saved ${OUT:-clip.mp4}, cost $(jq -c .usage <<<"$job")"

Details that bite

Use set -euo pipefail, but remember that a failed command inside a command substitution can still end the script before your message prints. The submit uses jq -e with an explicit fallback so you see the error body first.

Give every curl a --max-time. CI runners kill hung steps with no output, and a bounded timeout produces an error you can read. Do not put the API key on the curl command line with -u or in a logged variable; the script reads it from the environment into a header array.

A callback_url on the submit plus a webhook receiver is better for long renders, but CI has no public endpoint, so polling is the right default here.

  • Set ROW_ID from the pipeline run and step, never random.
  • Add a total deadline around the loop with timeout(1) if your runner has it.
  • Upload clip.mp4 as a build artifact, not into the repo.

Wiring it into a pipeline

In most CI systems a step fails on any nonzero exit, so exit 3 already stops the job. If you want a retry on exit 3 only, wrap the call in a small loop in the pipeline file that tests the code, runs the script once more, and gives up on exit 2 immediately. Keep ROW_ID identical across the retry only when the first job never started; after a failed render, use a new suffix so Sume creates a fresh job rather than returning the failed one.

Keep the prompt and parameters in version control next to the script, so a failed step can be reproduced from the commit alone.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume