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.mp4

GitLab 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.completed pick 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.