Poll a Sume job from a CI step with curl and jq: exit codes

A 23-line bash script that polls /v1/jobs/{id}/status and exits 0, 1, 2 or 3, so a CI step can tell done, failed, still running and a bad request apart.

4 min readSume
All posts

A CI step that waits on a Sume job needs four outcomes, not two: completed, failed or canceled, deadline reached with the job still running, and a request that can never succeed. The script below polls GET /v1/jobs/{id}/status with curl, reads sume_status with jq, honors next_poll_after_seconds, and exits 0, 1, 2 or 3 accordingly. On success it prints the artifact URLs, one per line.

Exit code 2 is the one most scripts get wrong. A deadline in your pipeline stops your watching, not the job: the docs say a client-side timeout does not cancel it, and it keeps running and billing. So the script says so, and you should store the id rather than resubmitting.

Exit codes

Behavior of the script, tested 2026-10-02 against a local mock of the documented status shape
ExitMeaningHTTP or status seen
0Job completed; URLs printedsume_status completed
1Job failed or canceled; error JSON on stderrsume_status failed or canceled
2Deadline reached, job may still runnon-terminal until the end
3Request cannot succeed by retrying401, 403, 404, other 4xx
(loops)Transient; sleeps and retries429, 5xx, connection failure

The script

Needs bash, curl and jq. Set SUME_API_KEY from your CI secret store, never inline. POLL_DEADLINE_SECONDS defaults to 1200, the 20 minutes the docs suggest for video.

#!/usr/bin/env bash
# Usage: poll.sh job_id   Exit: 0 completed, 1 failed or canceled, 2 deadline, 3 request error
set -u
BASE="${SUME_BASE_URL:-https://api.sume.com}"
DEADLINE=$((SECONDS + ${POLL_DEADLINE_SECONDS:-1200}))
job="$1"; tmp=$(mktemp); trap 'rm -f "$tmp"' EXIT
while (( SECONDS < DEADLINE )); do
  code=$(curl -sS --max-time 20 -o "$tmp" -w '%{http_code}' \
    -H "Authorization: Bearer ${SUME_API_KEY:?set SUME_API_KEY}" "$BASE/v1/jobs/$job/status")
  if [[ "$code" == 429 || "$code" == 5?? || "$code" == 000 ]]; then
    sleep "$(( ${RETRY_SLEEP:-5} ))"; continue          # transient: try again
  elif [[ "$code" != 200 ]]; then
    echo "HTTP $code: $(cat "$tmp")" >&2; exit 3           # 401, 404...: retrying cannot help
  fi
  status=$(jq -r '.data.sume_status' "$tmp")
  case "$status" in
    completed) jq -r '.data.job.result.artifacts[].url' "$tmp"; exit 0 ;;
    failed|canceled) jq -c '.data.job.error' "$tmp" >&2; exit 1 ;;
  esac
  sleep "$(jq -r '[(.data.next_poll_after_seconds // 2), 2] | max' "$tmp")"
done
echo "deadline reached; job $job may still be running" >&2
exit 2

Using it in a pipeline

Keep secrets out of logs: the script prints response bodies only on a non-retryable error, and those bodies contain a request id and message, not your key.

  • Submit with a stable Idempotency-Key so a re-run of the CI job does not bill a second render.
  • Capture the job id before polling and save it as a build artifact. On exit 2, the next run can resume by id.
  • Treat 1 as a content failure and 3 as a configuration failure when you route alerts.

Limits

I ran all four exit paths against a local mock: completed, failed, a one-second deadline, and a 404. It is a mock of the documented shapes, so verify the artifact path (.data.job.result.artifacts[].url) against one real completed job before you depend on it. The script has no retry cap for transient errors other than the deadline, and it polls sequentially; to wait on many jobs, run one copy per job or move to a proper client. Webhooks are cheaper for long renders if a public receiver is available.

Sources

Related posts

More in Integrations

All Integrations posts

Written by Sume