GitLab scheduled pipeline: run a nightly AI video job
Run a GitLab scheduled pipeline nightly, start the AI video with a date-based Idempotency-Key, and poll inside a job timeout above the run deadline.

A GitLab scheduled pipeline runs your .gitlab-ci.yml on a cron pattern you set under Build > Pipeline schedules, and a job can limit itself to those runs with rules: - if: $CI_PIPELINE_SOURCE == "schedule". For a nightly AI video, that job starts one run with an Idempotency-Key built from the pipeline's date, reads the API key from a masked CI/CD variable, and polls with backoff inside a job timeout set above the run's 90-minute deadline.
GitLab facts come from its docs listed under Sources, starting with Scheduled pipelines; Sume facts come from Create a run, Runs and results, and the Format cookbook. All were read on 2026-09-27. Sume has no GitLab integration: the job makes plain HTTPS calls with curl and jq. The same flow on GitHub, triggered by a release, is GitHub Actions: generate a release video.
How do I create a scheduled pipeline in GitLab?
In the project, select Build > Pipeline schedules, then New schedule. The form takes:
- An interval pattern: a preset, or any cron value, though schedules cannot run more often than the instance's maximum scheduled pipeline frequency.
- The target branch or tag the pipeline runs on.
- Inputs, up to 20, and CI/CD variables that exist only in this schedule's pipelines. GitLab recommends inputs over variables for pipeline configuration.
- You become the schedule owner, and the pipeline runs with your permissions. Select Run to start it now; manual runs are allowed once per minute.
Where should the API key live?
In a project CI/CD variable, never in the YAML. Sume's docs list CI secret stores among the places a key may live. GitLab's Visibility setting defaults to Masked since GitLab 18.3, which replaces the value with [MASKED] in job logs; a masked value must be a single line with no spaces and at least 8 characters. GitLab also warns that masking is not a guaranteed way to keep a value from malicious users. Selecting Protect variable limits it to pipelines on protected branches or tags, so point the schedule at a protected branch.
What does the nightly job look like?
The key and the body come from CI_PIPELINE_CREATED_AT, the pipeline's creation time in ISO 8601, UTC by default, so every retry inside that pipeline sends the same request. The image needs curl and jq. GitLab reports only the last command's result when commands share one string, so each failure path ends in an explicit exit 1. The loop backs off from 5 to 60 seconds, as Sume's cookbook recommends for a CI job that cannot expose an endpoint, and prints on every pass.
nightly-video:
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
timeout: 100 minutes # above Sume's 90-minute run deadline
retry: 2
script:
- |
DAY="${CI_PIPELINE_CREATED_AT%%T*}" # the pipeline's UTC date, same on every retry
jq -n --arg day "$DAY" '{input: {day: $day}}' > body.json
curl -sS -X POST https://api.sume.com/v1/formats/acme/nightly-recap/runs \
-H "Authorization: Bearer $SUME_API_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: nightly-recap-$DAY" -d @body.json -o run.json
RUN_ID=$(jq -er .data.id run.json) || { cat run.json; exit 1; }
- |
SLEEP=5
while :; do
STATUS=$(curl -sS "https://api.sume.com/v1/format-runs/$RUN_ID" \
-H "Authorization: Bearer $SUME_API_KEY" | jq -r '.data.status // "unknown"')
echo "$(date -u +%T) $STATUS" # steady output: silent jobs are dropped after an hour
case "$STATUS" in
completed) break ;;
failed|canceled|skipped) exit 1 ;;
esac
sleep "$SLEEP"; SLEEP=$(( SLEEP < 60 ? SLEEP * 2 : 60 ))
doneHow long can a GitLab job run while it waits?
Set the job timeout above Sume's deadline so GitLab does not fail the job first. If it stops anyway, the run keeps executing and spending; read it later by its id. A 429 or 503 during the loop is transient, which is why an unreadable status just means another pass. If you do run a public HTTPS endpoint, send communication.webhook_url and end the job after the create; Sume POSTs the result there once the run completes or fails.
| Limit | Value | Documented by |
|---|---|---|
| Project job timeout | 60 minutes by default; 10 minutes or more, under one month | GitLab |
Job timeout keyword | Can be longer than the project timeout, never longer than the runner's | GitLab |
| Project and runner timeouts both set | The lower value wins | GitLab |
| Job with no output | Dropped after one hour, regardless of the timeout | GitLab |
| Run deadline | At most 90 minutes after created_at, then force-finalized as failed | Sume |
| Long-form video | 15 to 30 minutes of work | Sume |
What happens when the job is retried?
Without retry, GitLab does not retry a job; with retry: 2, a failed job is processed up to two more times, for any failure type by default. Each retry sends the same key and body, so Sume answers 200 with the original run and idempotency_hit: true: no second run, no second charge, and the loop picks up the same run. A manual Run of the schedule on the same UTC date replays that run too.
Two cases need a new key. A run that ended failed stays bound to its key, so retry it with a new one. And for a deliberate second video on the same date, add a version you bump, such as nightly-recap-$DAY-v2. Idempotency keys for AI video APIs covers key design.
Sources
- Create a run
- Runs and results
- Format cookbook
- Errors and spend
- Authentication
- GitLab Docs: Scheduled pipelines (read 2026-09-27)
- GitLab Docs: Specify when jobs run with rules (read 2026-09-27)
- GitLab Docs: CI/CD variables (read 2026-09-27)
- GitLab Docs: Predefined CI/CD variables reference (read 2026-09-27)
- GitLab Docs: Customize pipeline configuration (read 2026-09-27)
- GitLab Docs: CI/CD YAML syntax reference (read 2026-09-27)
- GitLab Docs: Scripts and job logs (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.
- goose MCP server: add Sume's hosted MCP as an extension
Add a remote MCP server to goose: Sume's hosted MCP as a Streamable HTTP extension, OAuth by client registration, and approval for paid tools.
Written by Sume