Documentation
Generate from CI
The announcement video, made by the same pipeline that ships the release. One clip per tag, charged once, saved with the build.
1. A key and an actor
Create an API key with creatives:write and creatives:read, and give it a monthly credit limit so a busy month of tags cannot run past a budget. Store it as a CI secret named VIEWGC_KEY. Choose the actor who presents your releases (GET /v1/personas, or a digital twin of someone on your team) and store its id as VIEWGC_PERSONA. API quickstart.
2. The script
Commit this as scripts/release-video.sh and make it executable. It needs bash, curl and jq.
#!/usr/bin/env bash
# release-video.sh <tag> <what changed>: makes one clip and saves it as release-video.mp4
set -euo pipefail
API=https://api.viewgc.com
TAG="$1"; WHAT="$2"
# The tag is the idempotency key: a retried job gets the first answer back
# and is charged once.
CAMPAIGN=$(curl -sS --fail-with-body "$API/v1/studio/generate" \
-H "Authorization: Bearer $VIEWGC_KEY" \
-H "Idempotency-Key: release-$TAG" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg p "$VIEWGC_PERSONA" --arg w "$WHAT" \
'{personaId: $p, prompt: ("What is new in this release: " + $w), aspectRatio: "9:16"}')" \
| jq -r .campaignId)
# A clip takes a few minutes. Wait up to 20.
for i in $(seq 1 80); do
C=$(curl -sS --fail-with-body "$API/v1/creatives?campaignId=$CAMPAIGN" -H "Authorization: Bearer $VIEWGC_KEY" | jq '.data[0]')
STATUS=$(echo "$C" | jq -r '.status // "QUEUED"')
[ "$STATUS" = COMPLETED ] && break
[ "$STATUS" = FAILED ] && { echo "$C" | jq -r .failureMessage; exit 1; }
sleep 15
done
[ "$STATUS" = COMPLETED ] || { echo "Still generating after 20 minutes; it will finish on its own (see Jobs in the studio)."; exit 1; }
URL=$(curl -sS --fail-with-body "$API/v1/creatives/$(echo "$C" | jq -r .id)/download-link" -H "Authorization: Bearer $VIEWGC_KEY" | jq -r .url)
curl -sSfo release-video.mp4 "$URL"
echo "Saved release-video.mp4"3. Run it on a tag
GitHub Actions:
# .github/workflows/release-video.yml
name: Release video
on:
push:
tags: ['v*']
jobs:
video:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Make the release video
env:
VIEWGC_KEY: ${{ secrets.VIEWGC_KEY }}
VIEWGC_PERSONA: ${{ vars.VIEWGC_PERSONA }}
run: ./scripts/release-video.sh "${GITHUB_REF_NAME}" "$(git tag -l --format='%(contents:subject)' "${GITHUB_REF_NAME}")"
- uses: actions/upload-artifact@v4
with:
name: release-video
path: release-video.mp4GitLab CI:
# .gitlab-ci.yml
release-video:
rules:
- if: $CI_COMMIT_TAG
image: alpine:3.20
before_script: [apk add --no-cache bash curl jq git]
script:
- ./scripts/release-video.sh "$CI_COMMIT_TAG" "$CI_COMMIT_TAG_MESSAGE"
artifacts:
paths: [release-video.mp4]Good to know
- Re-running the job for the same tag returns the same clip and charges nothing more, because the tag is the idempotency key.
- Rather than waiting in the job, you can end it after the request and let a webhook for
creative.completedpick the video up. - A 402 means the workspace is out of API credits or the key reached its monthly limit; the message says which.
- The downloaded file says it is AI-generated in its own metadata.
Something not covered here? Ask us, or use Help inside the studio.