Render cron job that submits a Sume job: one key per slot

Render delays a cron run while one is active, and a manual trigger cancels it. Use a UTC slot Idempotency-Key so a re-run never bills a Sume job twice.

4 min readSume
All posts

On Render, make the cron job submit one Sume job in webhook mode and derive the Idempotency-Key from the schedule slot (the UTC hour or day), not from a random value. Render can re-run or cancel a run, and a slot key makes the second submit return the first job instead of billing again.

What Render's cron runner does

Render documents guarantees that shape the key design. The table is from the vendor page (read 2026-10-10).

Render cron job behavior, from render.com/docs/cronjobs (read 2026-10-10)
BehaviorRender saysWhat it means for a Sume submit
Active runsAt most one run of a cron job is active at a time; a due run is delayed until the active one finishesA slow run does not overlap, but the delayed run still fires later
Manual triggerA manual trigger cancels any active runA cancel can land after you submitted but before you logged the job id
Time zoneAll day and time ranges use UTCBuild the slot from UTC
Max durationRender stops an active run after 12 hoursSubmit and exit; never wait for a render inside the cron

The script

Set SUME_API_KEY as a Render environment variable. The request body follows the Image 1.0 fields in the Image 1.0 docs (prompt, quality, aspect_ratio, mode, webhook_url). The slot is the UTC date, so a nightly job has one key per night.

import json, os, urllib.request
from datetime import datetime, timezone

now = datetime.now(timezone.utc)
slot = now.strftime("%Y-%m-%d")
body = {
    "prompt": "Soft daylight product shot on a linen cloth",
    "quality": "low",
    "aspect_ratio": "4:5",
    "mode": "webhook",
    "webhook_url": "https://example.com/hooks/sume",
}
req = urllib.request.Request(
    "https://api.sume.com/v1/image-1.0/generate",
    data=json.dumps(body).encode(),
    headers={
        "Authorization": "Bearer " + os.environ["SUME_API_KEY"],
        "Content-Type": "application/json",
        "Idempotency-Key": "nightly-hero-" + slot,
    },
)
with urllib.request.urlopen(req, timeout=20) as r:
    print(r.status, r.read().decode())

Handle the cancel-and-rerun case

If a manual trigger cancels the run after the HTTP call left, the retry uses the same key and gets the original job back. If you change the prompt for the same slot, expect 409 idempotency_conflict; put the revision in the key (nightly-hero-2026-10-10-v2) instead of reusing it.

  • Key = purpose + UTC slot, never a timestamp or UUID made at run time.
  • Exit after the 2xx response and let the webhook carry the result.
  • On 429 or 503 retry with the same key; on 4xx validation errors stop.

Why a webhook and a slot key together

Submit in webhook mode so the scheduled process can exit. Sume sends only terminal events (job.completed, job.failed, job.canceled) and retries up to 10 attempts, 30 seconds apart, with a 10 second timeout for each attempt, per the Webhooks page. Use job_id as the idempotency key on your receiver.

Delivery is an optimization, not the only recovery path. Keep GET /v1/jobs/:id/status polls available for events that never arrive. A repeat of the same key with the same body is an exact retry. A repeat with a different body returns 409 idempotency_conflict, as the Generation admission page lists.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume