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.

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
| Part | What it does | Docs basis |
|---|---|---|
| secrets.SUME_API_KEY | Bearer key for the API | Authorization: Bearer header |
| Idempotency-Key from the commit SHA | A rerun of the job returns the same video job | Replay returns the original job |
| Poll every 30 s with a deadline | Waits without a long HTTP call | 30 s poll suggested; sync caps at 30 s |
| unsigned_urls[0] download | Fetches the finished mp4 | Content URL on a completed job |
| upload-artifact | Keeps the file with the run | Your 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
errorfield 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_dispatchor a version tag, not on every push. - Set a job-level
timeout-minutesslightly 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
Authorizationheader stay inside thecurlcall. - 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
- stt_create dry_run and max_spend_usd: cap an MCP transcription run
On Sume's hosted MCP, dry_run previews admission and cost without submitting, and max_spend_usd caps a paid stt_create. Add an idempotency_key to every write.
- Can I swap a Sume MCP OAuth token for an API key? Why you should not
A hosted MCP OAuth token is not a Sume API key. Do not mint keys as a workaround for a read-only session; turn Write on at consent, or use a key on purpose.
- Trae IDE: add Sume as a remote MCP server with Add Manually
In Trae, open Settings > MCP > Add > Add Manually and paste a JSON entry with Sume's hosted URL and an Authorization header. Steps, trade-offs, key handling.
- v0 custom MCP: connect Sume with a Bearer token or OAuth
In v0, open the + menu, choose MCPs, add a custom MCP and pick Bearer Token or OAuth for auth. Which to use for Sume's hosted server, and what each grants.
Written by Sume