GitHub Actions job that renders a 9:16 clip on Sume as an artifact

A workflow file that replaces a Sora render step: submit to Sume, poll with a deadline, and upload the mp4 as a build artifact. The key stays in a secret.

5 min readSume
All posts

To render a video in CI, store SUME_API_KEY as a repository secret, submit to POST /v1/videos with an idempotency key built from the commit, poll until completed, and upload the downloaded mp4 as an artifact. A release job that called the OpenAI Sora API stopped working when the API ended on 2026-09-24 (Magic Hour tracker, read 2026-10-06), and this is the minimal replacement step.

The workflow below is one job with one shell step. It uses only curl and jq, which a hosted Ubuntu runner provides, and it never prints the key.

What each part does

Workflow parts, read 2026-10-06
PartWhat it doesDocs basis
secrets.SUME_API_KEYBearer key for the APIAuthorization: Bearer header
Idempotency-Key from the commit SHAA rerun of the job returns the same video jobReplay returns the original job
Poll every 30 s with a deadlineWaits without a long HTTP call30 s poll suggested; sync caps at 30 s
unsigned_urls[0] downloadFetches the finished mp4Content URL on a completed job
upload-artifactKeeps the file with the runYour storage, not the vendor's

The workflow

Put this in .github/workflows/render.yml. It renders a 5-second vertical clip on Omni at 720p, which costs $0.63 per run.

name: render-clip
on: workflow_dispatch
jobs:
  render:
    runs-on: ubuntu-latest
    steps:
      - name: Render
        env:
          SUME_API_KEY: ${{ secrets.SUME_API_KEY }}
        run: |
          set -euo pipefail
          H="Authorization: Bearer $SUME_API_KEY"
          POLL=$(curl -sf -X POST https://api.sume.com/v1/videos -H "$H" \
            -H "Idempotency-Key: ci-${GITHUB_SHA}" -H 'Content-Type: application/json' \
            -d '{"model":"gemini-omni-flash-1.1","prompt":"A paper plane glides over a desk, soft light","aspect_ratio":"9:16","resolution":"720p","duration":5}' \
            | jq -r .polling_url)
          for i in $(seq 1 30); do
            sleep 30
            S=$(curl -sf "$POLL" -H "$H")
            case $(echo "$S" | jq -r .status) in
              completed) curl -sf -H "$H" -o clip.mp4 "$(echo "$S" | jq -r '.unsigned_urls[0]')"; exit 0;;
              failed|cancelled) echo "$S" | jq .error; exit 1;;
            esac
          done
          echo timeout; exit 1
      - uses: actions/upload-artifact@v4
        with: {name: clip, path: clip.mp4}

Why it is shaped this way

  • The idempotency key uses the commit SHA, so a re-run of the same commit returns the same job and does not bill twice. A manual change of the prompt in the file needs a new commit, which gives a new key.
  • The loop has a fixed ceiling of 30 polls, so a stuck job fails the run in 15 minutes instead of holding a runner.
  • The download sends the bearer header in case the content URL needs it; the docs show both the open and authenticated forms.
  • On a failed job the step prints the error field and exits non-zero, so the run is red.

Two limits to respect. Hosted runners are not a place for a hundred renders; use a queue and a callback for volume, as in the exit and feature-flag post for how to switch a feature over. And artifacts expire on GitHub's schedule, so copy anything you keep long term to your own storage, as the file-lifetime post explains for Sume results.

Hardening the job

The workflow above is the shortest version that works. Before it runs on every push, add the guards a paid step needs. Trigger it on a tag or on a manual dispatch instead of every commit, because every new commit is a new idempotency key and so a new paid job. A pull request from a fork does not receive repository secrets, so the step should be skipped there rather than fail with an empty key.

Also decide what a failed render does to the pipeline. For a marketing asset, a red run that blocks a release is probably wrong; mark the job as allowed to fail, and have the next step use the previous artifact. For a release video that must exist, keep it red.

Add a final step that writes the job id and cost to the run summary. A reviewer who opens the run then sees which model rendered the clip, what it cost and where the artifact is, without opening the log, and a finance question about CI spend can be answered from run summaries alone.

  • Run on workflow_dispatch or a version tag, not on every push.
  • Set a job-level timeout-minutes slightly above the poll ceiling, so a hung runner is cut off even if the script misbehaves.
  • Never echo the key. If you print the request, print the body only, and let the Authorization header stay inside the curl call.
  • Pin the model id and settings in the workflow file, so a render is reproducible from the commit alone.
  • Upload the artifact even when the clip is short; a retention of a few days is enough for review.

Sources

Related posts

More in Integrations

All Integrations posts

Written by Sume