GitHub Actions: generate a release video with a Sume Format
Start a Sume Format run when a GitHub release is published, pass the notes as input, poll until the video is ready, and attach it to the release.

To generate a release video with GitHub Actions, run a workflow on the release event with types: [published], start a run of your Sume Format with the release notes in input and the tag in the Idempotency-Key, poll the run until it finishes, then download primary_output_url and attach the file with gh release upload.
Sume has no GitHub Action, and the Sume CLI ships no video command, so the Sume steps are plain HTTPS calls with curl. Sume facts come from Create a run, Runs and results, and the Format cookbook; GitHub behavior comes from GitHub's own docs, read 2026-09-27. For the Sume CLI in a pipeline, see How to run the Sume CLI in CI.
What do I need before the first run?
Three things, set up once:
- A Format of your own that turns release notes into a video; this post uses the placeholder address
acme/release-video. See What is a Sume Format? - A Sume API key with
formats:readandformats:write. A team Format needs a key created in that team's workspace, and service-account keys cannot create Format runs. - The key saved as a repository secret named
SUME_API_KEY. Sume's docs list CI secret stores among the places a key may live. If the secret is missing, GitHub's expression returns an empty string, and Sume answers401 unauthorized.
How do I start the run when a release is published?
For a release event, GITHUB_REF is the tag ref refs/tags/<tag_name>, so GITHUB_REF_NAME is the tag. The job copies the release description from the event payload into NOTES, builds the request body with jq, starts the run, and saves the run id for the next step:
on:
release:
types: [published]
permissions:
contents: write
jobs:
video:
runs-on: ubuntu-latest
timeout-minutes: 100
env:
SUME_API_KEY: ${{ secrets.SUME_API_KEY }}
NOTES: ${{ github.event.release.body }}
steps:
- name: Start the Format run
run: |
jq -n --arg tag "$GITHUB_REF_NAME" --arg notes "$NOTES" \
'{input: {tag: $tag, release_notes: $notes}, generation_spend_cap_usd: 20}' > body.json
curl -sS -X POST https://api.sume.com/v1/formats/acme/release-video/runs \
-H "Authorization: Bearer $SUME_API_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: release-video-$GITHUB_REF_NAME-v1" -d @body.json > run.json
RUN_ID=$(jq -er .data.id run.json) || { cat run.json; exit 1; }
echo "RUN_ID=$RUN_ID" >> "$GITHUB_ENV"Why do the notes go in input and the tag in the key?
Each choice follows a rule on one side:
NOTESis an intermediate environment variable on purpose. GitHub treats contexts ending inbodyas potentially untrusted input, and for inline scripts it calls an intermediate environment variable the preferred approach.- The notes go in
input, notinstruction. Sume writesinputto a file in the run's workspace and tells the agent it is caller-supplied data, not instructions.inputtakes at most 64 top-level keys and 2 MiB. - The key comes from the tag. The same key with the same body returns
200and the original run, so re-running the workflow does not pay twice. Bump-v1when you want a fresh video for the same tag. generation_spend_cap_usdbounds what this run may spend, up to $500. Spend caps for unattended agents covers the rules.
How does the job wait for the video?
Sume's cookbook names a CI job among the cases for polling with backoff instead of a webhook. The second step reads GET /v1/format-runs/{run_id} with a gap that starts at 5 seconds and doubles to 60, and treats a failed read as another wait: a 429 or 503 mid-loop is transient while the run keeps working. RUN_ID arrives through GITHUB_ENV, which passes a variable to later steps in the job.
completed fills primary_output_url, the one thing to show, and Sume media URLs are durable and public, so the MP4 download needs no Sume key. failed, canceled, and skipped are terminal too; the step prints a failed run's error and fails the job.
The GitHub CLI is preinstalled on GitHub-hosted runners, and each step that uses it needs GH_TOKEN. The job requests contents: write, which GitHub's workflow syntax reference says allows the action to create a release. With --clobber, a re-run replaces an asset of the same name:
- name: Wait for the video and attach it
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
SLEEP=5
while :; do
RUN=$(curl -sS "https://api.sume.com/v1/format-runs/$RUN_ID" \
-H "Authorization: Bearer $SUME_API_KEY") || RUN='{}'
case "$(echo "$RUN" | jq -r '.data.status // empty')" in
completed) break ;;
failed|canceled|skipped) echo "$RUN" | jq '.data.error'; exit 1 ;;
esac
sleep "$SLEEP"; SLEEP=$(( SLEEP < 60 ? SLEEP * 2 : 60 ))
done
curl -sSL --fail -o release-video.mp4 "$(echo "$RUN" | jq -r .data.primary_output_url)"
gh release upload "$GITHUB_REF_NAME" release-video.mp4 --clobber --repo "$GITHUB_REPOSITORY"What are the time limits on each side?
Keep timeout-minutes above Sume's 90-minute ceiling so GitHub does not cancel the job first. If GitHub stops it anyway, the Sume run keeps running and billing; read it back later by its id.
| Limit | Value | Documented by |
|---|---|---|
Sume run deadline (expires_at) | 90 minutes after created_at, or sooner once a run older than 25 minutes has been silent for 10 | Sume |
| Typical long-form host video | 15 to 30 minutes | Sume |
| GitHub-hosted job execution time | Up to 6 hours | GitHub |
timeout-minutes when omitted | 360 | GitHub |
| Re-runs of one workflow run | 50 | GitHub |
Why didn't my workflow run, or why did it fail?
Causes to check, from both sides:
- The release is still a draft. GitHub does not trigger workflows for created, edited, or deleted drafts; publishing one fires
published. - Another workflow published the release with
GITHUB_TOKEN. Events triggered by that token do not create new workflow runs, andreleaseis not among the exceptions GitHub lists. 401 unauthorized: the secret is missing, or the key was revoked.403 insufficient_scope: the key lacksformats:writeor predates the Formats API. Create a new key; scopes cannot be added later.409 idempotency_conflict: the tag's key arrived with a different body, for example a release published again with edited notes. Bump the version in the key.
Sources
- Create a run
- Runs and results
- Format cookbook
- Format API
- Authentication
- CLI command reference
- GitHub Docs: Events that trigger workflows (read 2026-09-27)
- GitHub Docs: Using secrets in GitHub Actions (read 2026-09-27)
- GitHub Docs: Workflow syntax for GitHub Actions (read 2026-09-27)
- GitHub Docs: Actions limits (read 2026-09-27)
- GitHub Docs: Variables reference (read 2026-09-27)
- GitHub Docs: Contexts reference (read 2026-09-27)
- GitHub Docs: Workflow commands for GitHub Actions (read 2026-09-27)
- GitHub Docs: Script injections (read 2026-09-27)
- GitHub Docs: Secure use reference (read 2026-09-27)
- GitHub Docs: GITHUB_TOKEN (read 2026-09-27)
- GitHub Docs: Using GitHub CLI in workflows (read 2026-09-27)
- GitHub Docs: REST API endpoints for releases (read 2026-09-27)
- GitHub CLI manual: gh release upload (read 2026-09-27)
Related posts
More in Integrations
- Golang: verify a webhook signature with HMAC and hmac.Equal
Verify a Sume webhook in Go: read the body once with io.ReadAll, HMAC-SHA256 the timestamp and raw bytes, then hmac.Equal each sume-v1 entry.
- Google ADK MCP tools: connect McpToolset to Sume's server
Add Sume's hosted MCP server to a Google ADK agent with McpToolset, an API-key header, and tool_filter, and make paid tools ask for confirmation.
- Google Sheets to video automation with Apps Script and Sume
Turn up to 100 sheet rows into Sume video runs with one Apps Script request, then poll the queue from a time-driven trigger and write URLs back.
- Gradio video generation app: Sume API key in a Space secret
Build a Gradio video generation app on the Sume API: the key stays in a Hugging Face Space secret, a generator polls the job, and gr.Video plays it.
Written by Sume