Supabase cron Edge Function: start an AI video on a schedule
Schedule a Supabase Edge Function with pg_cron and pg_net, keep the video API key in the function's secrets, and start one date-keyed run per day.

To run a Supabase Edge Function on a cron schedule, enable Supabase Cron (the pg_cron extension) and pg_net, then schedule a job whose SQL calls net.http_post on the function's URL, with the URL and key read from Supabase Vault. For an AI video, the function, not the SQL, holds the video API key: it starts the run with a date-keyed Idempotency-Key and a webhook URL, and returns.
Supabase facts come from its Scheduling Edge Functions, Cron, pg_net, and Edge Functions pages under Sources; Sume facts come from Create a run, Runs and results, and Authentication. All were read on 2026-09-27. Sume has no Supabase connector: the function makes a plain HTTPS call. The receiving side is in Supabase Edge Function webhook for Sume.
How do I enable cron in Supabase with a SQL query?
Supabase Cron runs on the pg_cron extension, and pg_net lets a job make HTTP requests. Enable both with the SQL below, or enable pg_cron from the Cron module under Integrations in the Dashboard. Two cautions from Supabase: the pg_net API is in beta, so function signatures may change, and disabling pg_cron permanently deletes all jobs.
create extension pg_cron with schema pg_catalog;
grant usage on schema cron to postgres;
grant all privileges on all tables in schema cron to postgres;
create extension pg_net with schema "extensions";How do I schedule an Edge Function with pg_cron?
Supabase's recipe stores the project URL and key in Vault, then schedules net.http_post against the function. The version below adds a cron_secret: Supabase calls the publishable key safe to use in a browser, so holding it proves nothing about the caller. Job names are case sensitive, and a second job with the same name overwrites the first. Supabase's examples annotate schedules in GMT, so 0 6 * * * is 06:00 GMT. net.http_post waits 2000 ms by default; Supabase's own sub-minute example sets timeout_milliseconds to 5000.
select vault.create_secret('https://project-ref.supabase.co', 'project_url');
select vault.create_secret('YOUR_SUPABASE_PUBLISHABLE_KEY', 'publishable_key');
select vault.create_secret('a-long-random-string', 'cron_secret');
select cron.schedule(
'daily-recap-video',
'0 6 * * *', -- 06:00 GMT every day
$$
select net.http_post(
url:= (select decrypted_secret from vault.decrypted_secrets where name = 'project_url')
|| '/functions/v1/start-daily-video',
headers:= jsonb_build_object(
'Content-Type', 'application/json',
'apikey', (select decrypted_secret from vault.decrypted_secrets where name = 'publishable_key'),
'x-cron-secret', (select decrypted_secret from vault.decrypted_secrets where name = 'cron_secret')
),
timeout_milliseconds:= 5000
) as request_id;
$$
);What should the Edge Function send to the video API?
Set SUME_API_KEY and CRON_SECRET with supabase secrets set. Secrets are available immediately, with no redeploy, and a name cannot start with SUPABASE_. The function keeps the starter template's key check, adds its own secret check, and starts one run of a saved Sume recipe, a Format. The key and the body come from the UTC date alone, so a repeat that day sends an identical request. Store data.id against the date: 202 is a new run, 200 a replay of the day's run.
import { withSupabase } from "npm:@supabase/server@^1";
export default {
fetch: withSupabase({ auth: ["publishable", "secret"] }, async (req) => {
const cronSecret = Deno.env.get("CRON_SECRET");
if (!cronSecret || req.headers.get("x-cron-secret") !== cronSecret) {
return new Response("forbidden", { status: 403 });
}
const day = new Date().toISOString().slice(0, 10); // UTC date
const res = await fetch("https://api.sume.com/v1/formats/acme/daily-recap/runs", {
method: "POST",
headers: {
Authorization: `Bearer ${Deno.env.get("SUME_API_KEY")}`,
"Content-Type": "application/json",
"Idempotency-Key": `daily-recap-${day}`,
},
body: JSON.stringify({
input: { day },
communication: { webhook_url: "https://project-ref.supabase.co/functions/v1/sume-webhook" },
}),
});
const { data, error } = await res.json();
if (!res.ok) return Response.json(error, { status: res.status });
return Response.json({ run_id: data.id, replay: data.idempotency_hit });
}),
};Why not call the video API straight from SQL?
pg_net can POST JSON, but it fits this job badly. Every job is stored on the cron.job table, so a key typed into the command sits in a table, while Sume's docs say to keep API keys on trusted servers. net.http_post is also asynchronous: it returns only a request id, and the response lands in net._http_response, kept for 6 hours by default. The SQL never sees the run id it should store; the function does.
What if the job fires twice or the call times out?
The date key makes any repeat that day safe. Two of Sume's idempotency rules matter here; Idempotency keys for AI video APIs covers the rest:
- Same key, same body:
200with the original receipt andidempotency_hit: true. No second run, no second charge. - Same key, different body:
409 idempotency_conflict, and nothing runs. Thewebhook_urlis part of the body, so keep it fixed. - To see what happened, read
cron.job_run_details, which records every job run and its status, andnet._http_response, which has the function'sstatus_codeand atimed_outflag.
| Piece | Limit or default |
|---|---|
| Supabase Cron | Supabase recommends no more than 8 jobs running at once, each for no more than 10 minutes |
net.http_post | 2000 ms default timeout; responses kept 6 hours |
| Edge Function wall clock | 150 s on Free, 400 s on paid plans |
| Long-form Sume video | 15 to 30 minutes of work |
| Sume run deadline | At most 90 minutes after created_at, then force-finalized as failed |
Should the function wait for the video?
No. Long-form video takes longer than either Edge Function wall clock in the table above, and a function that stops watching does not stop the run or its spend. communication.webhook_url gets one signed POST when the run completes or fails, so a second Edge Function receives the result; Supabase Edge Function webhook for Sume shows it with verify_jwt = false and an HMAC check. The same trigger pattern on Cloudflare is Cloudflare Workers cron job.
Sources
- Create a run
- Runs and results
- Errors and spend
- Authentication
- Supabase: Scheduling Edge Functions (read 2026-09-27)
- Supabase: Cron (read 2026-09-27)
- Supabase: Cron: Install (read 2026-09-27)
- Supabase: Cron: Quickstart (read 2026-09-27)
- Supabase: pg_net: Async Networking (read 2026-09-27)
- Supabase: Environment variables (read 2026-09-27)
- Supabase: Edge Functions: Limits (read 2026-09-27)
- Supabase: Getting Started with Edge Functions (read 2026-09-27)
- Supabase: Handling Stripe Webhooks (read 2026-09-27)
Related posts
More in Integrations
- Telegram bot to generate video: a Sume job, then sendVideo
A Telegram bot can turn /video into a Sume job, reply at once, then call sendVideo with the artifact URL when Sume's signed webhook arrives.
- Temporal workflow signal from an AI video webhook
Start the video job in a Temporal activity, wait on a signal your webhook receiver sends by workflow id, and keep a timer that reads the run anyway.
- Test Sume webhooks locally with ngrok or a Cloudflare Tunnel
Sume refuses localhost webhook URLs. Expose your handler with ngrok or a Cloudflare Quick Tunnel, send a signed test, then redeliver real events.
- Text-to-video API in Python: submit, poll, and download
Call Sume's text-to-video API from Python with Requests: POST /v1/videos, poll with timeouts, then stream the MP4 the content route redirects to.
Written by Sume