Cloud Scheduler trigger for a Cloud Run job, retry-safe
Add a Cloud Scheduler trigger to a Cloud Run job with a cron and a time zone. A failed task retries 3 times by default, so key paid calls to the date.

To run a Cloud Run job on a schedule, add a Cloud Scheduler trigger to it: on the job's Triggers tab, choose Add Scheduler Trigger and give it a unix-cron frequency and a time zone, or create a Cloud Scheduler HTTP job that POSTs to the job's :run URL. A job consists of one or more tasks, and a failed task restarts up to 3 times by default, so a task that calls a paid API must key that call to the date: then every restart replays the same request instead of paying again.
Google Cloud facts come from Execute jobs on a schedule and the other Google pages under Sources; Sume facts come from Create a run, Runs and results and Errors and spend. All were read on 2026-09-28. Sume has no Google Cloud integration: the task makes one plain HTTPS call. The AWS version of this pattern is How to run a Lambda function on a schedule.
How do I create the Cloud Scheduler trigger?
Enable the Cloud Scheduler API first (gcloud services enable cloudscheduler.googleapis.com). You need Cloud Scheduler Admin, or a custom role with cloudscheduler.jobs.create, plus Cloud Run Invoker to execute jobs with the gcloud CLI. In the console, open the job, click the Triggers tab, then Add Scheduler Trigger:
- Name and region: the Scheduler job's region does not need to match the Cloud Run job's.
- Frequency in unix-cron format, for example
0 12 * * *, and a Timezone. - A service account with permission to invoke the job.
gcloud scheduler jobs create http daily-recap-trigger \
--location=us-central1 \
--schedule="0 6 * * *" \
--time-zone="America/New_York" \
--uri="https://run.googleapis.com/v2/projects/PROJECT_ID/locations/us-central1/jobs/daily-recap:run" \
--http-method=POST \
--oauth-service-account-email=PROJECT_NUMBER-compute@developer.gserviceaccount.comWhy can a scheduled job call the API more than once?
Because a restarted task runs your code again, POST included. A container that exits non-zero has failed, and Cloud Run restarts the task up to the job's maximum retries. With retries on, the task timeout applies to each attempt, and an attempt that doesn't finish in time is stopped. Cloud Scheduler can retry its own call if you configure it, and a person can also execute the job from the console or the gcloud CLI.
| Setting | Default | Range | Applies to |
|---|---|---|---|
| Maximum retries | 3 | 0 to 10 | Each task, not the whole job |
| Task timeout | 10 minutes | Up to 168 hours (1 hour with GPUs) | Each attempt of a task |
Scheduler --max-retry-attempts | 0 | 0 to 5 | The Scheduler's own call |
Scheduler --time-zone | Etc/UTC | A tz database name | The schedule |
What should the task send to the paid API?
One create keyed to the UTC date, with a body that is also fixed for the day. On a restart, Sume answers the same key and body with 200, the original receipt and idempotency_hit: true, so no second run starts. Idempotency keys for AI video APIs covers the other replay cases, and a run that ended failed needs a new key, as Sume Format run failed explains. Don't build the key from CLOUD_RUN_TASK_ATTEMPT: that counter starts at 0 and goes up on every retry, so each restart would become a new paid run. And schedule the trigger well away from midnight UTC, so a restart can't compute the next day's date.
Store the API key in Secret Manager, which Google recommends for API keys, grant the job's service identity Secret Manager Secret Accessor, and expose the key with gcloud run jobs update daily-recap --set-secrets SUME_API_KEY=sume-api-key:1. Google recommends pinning a version for environment variables, which are resolved at instance startup. urlopen raises HTTPError on an error status, the error still carries its status code and body, and the exit code tells Cloud Run the task failed.
import datetime, json, os, sys, urllib.error, urllib.request
day = datetime.datetime.now(datetime.timezone.utc).date().isoformat()
body = {
"input": {"day": day}, # fixed for the day: same key, same body
"communication": {"webhook_url": "https://example.com/hooks/sume"},
}
req = urllib.request.Request(
"https://api.sume.com/v1/formats/acme/daily-recap/runs",
data=json.dumps(body).encode(),
headers={
"Authorization": f"Bearer {os.environ['SUME_API_KEY']}",
"Content-Type": "application/json",
"Idempotency-Key": f"daily-recap-{day}", # unchanged on every restart
},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
run = json.load(resp)["data"] # 202: new run, 200: replay
except urllib.error.HTTPError as err:
print(err.code, err.read().decode()) # Sume's JSON error
sys.exit(1) # non-zero: the task failed and may restart
print(run["id"], run["idempotency_hit"])Should the task wait for the video to finish?
No. Long-form video is 15 to 30 minutes of work, longer than the default 10-minute task timeout, and an attempt that runs out of time is stopped. Stopping the wait never stops the run or its spend. Submit, print the run id, and exit 0. Sume POSTs one signed format.run.terminal receipt to communication.webhook_url when the run completes or fails, so any public HTTPS endpoint you run can take the result; Signed webhooks for Sume video runs shows the receiving side. If you poll instead, a run is finalized as failed at most 90 minutes after creation, so a task timeout above that covers the whole run.
Sources
- Create a run
- Runs and results
- Errors and spend
- Cloud Run: Execute jobs on a schedule (read 2026-09-28)
- Cloud Run: Set maximum retries for jobs (read 2026-09-28)
- Cloud Run: Set task timeout for jobs (read 2026-09-28)
- Cloud Run: Configure secrets for jobs (read 2026-09-28)
- Cloud Run: Container runtime contract (read 2026-09-28)
- gcloud scheduler jobs create http reference (read 2026-09-28)
- Cloud Scheduler: Retry jobs (read 2026-09-28)
- Python: urllib.request (read 2026-09-28)
- Python: urllib.error (read 2026-09-28)
Related posts
More in Integrations
- Codex MCP tool timeout: tool_timeout_sec and slow jobs
Codex gives each MCP tool call 60 seconds by default. Raise tool_timeout_sec per server in config.toml, or keep slow media jobs inside the limit.
- Continue MCP server: add Sume in .continue/mcpServers
Add Sume's hosted MCP server to Continue with a YAML block in .continue/mcpServers: type streamable-http, the URL, and a key from a secret.
- Copilot CLI MCP server: add Sume with copilot mcp add
Add a remote MCP server to GitHub Copilot CLI with copilot mcp add: Sume's hosted MCP, a key header or OAuth, and a timeout above 55 seconds.
- CrewAI MCP server: connect an agent to Sume's tools
Give a CrewAI agent Sume's hosted MCP tools with MCPServerHTTP in the mcps field: a Bearer key header, a tool filter, and short jobs_wait slices.
Written by Sume