Internal: The Scheduled Tick
The external-scheduler entry point (Supabase pg_cron / GitHub Action / any scheduler). One call runs one platform tick, strictly ordered: pipeline pass → per-instance funnel metrics → pending static renders → due nurture sends → the two-phase video pass.
| Endpoint | Auth | Rate limit | Body |
|---|---|---|---|
POST /api/cron |
Authorization: Bearer <CRON_SECRET> |
none | ignored |
POST /api/cron
Auth
Byte-exact Bearer ${CRON_SECRET}, constant-time compare. Three tiers:
CRON_SECRETunset → 401 for everyone (the endpoint cannot be authorized).- Secret valid, Supabase unconfigured → 500 plain-text
service client unavailable. There is no demo tier here — past the auth check the secret is set, so a missing service client is a misconfiguration (SUPABASE_SECRET_KEYabsent or misspelled), not a keyless run. It fails loud on purpose (#17): a silent 200 would make every tick a no-op forever with no signal, halting metric-sync and nurture platform-wide. - Fully wired → live tick.
⚠️ A CRON_SECRET under 16 chars or matching placeholder patterns (changeme, …) makes requireSecret throw outside the try/catch — you'll see a 500, not a 401. Fix the env var; don't retry.
Responses
| Status | Body |
|---|---|
| 200 (live) | { "ok": true, "instances": n, "pipelineActions": [...], "pipelineErrors": n, "funnelErrors": n, "nurtureSent": n } |
| 401 | plain-text unauthorized (not JSON) |
| 500 | plain-text service client unavailable — authorized, but SUPABASE_SECRET_KEY is missing/misspelled. Fix the env var; retrying will not help |
| 500 | plain-text cron job error — the tick itself threw |
pipelineActions values: advanced:<stage>, generated:<stage>, revised:positioning|angles, requested:meta_launch, awaiting:positioning_confirm|angle_choice|page_publish|meta_launch, waiting:intake|creative_approval|llm, noop:live.
The revised:* pair is emitted when a rejected gate sends a stage back for another pass (src/lib/pipeline/worker.ts — the PipelineAction union is the authority). A consumer switching exhaustively on this vocabulary would previously have hit an unhandled value.
What a tick does (live)
- Pipeline pass — every
active/provisioninginstance, up to 10 decision steps, at most one LLM generation per instance per pass; may open approvals and post jargon-guarded Conductor lines. Generation gated onANTHROPIC_API_KEYand the daily token budget (spent budget →waiting:llm, pipeline still advances on evidence). - Funnel metrics — idempotent daily
metric_recordsupsert per instance. - Static renders — pending
kind='static'creatives (storage_path IS NULL), bounded per pass. - Nurture — due enrollment steps (batched queries; suppressed/consent-blocked sends count in
nurtureSent). - Video pass (last) — two-phase: collect finished provider jobs (bounded polls), then submit new drafts (CAS-claimed, budget-capped). No
GEMINI_API_KEY→ all video rows skipped, nothing charged.
Notes for operators
- A 200 does not mean everything succeeded — per-instance failures are swallowed by design; inspect
pipelineErrors/funnelErrors. Static-render and video results are console-logged only. - Idempotent by design: safe to re-run forever. Approvals only open when none of that type is pending; renders key on null
storage_path; metrics upsert; video submits are claim-guarded. maxDuration = 300— the function declares a 5-minute ceiling; schedule accordingly.- Deploy note: call it on the canonical origin — a
.netlify.appalias 301s and strips the Authorization header (guarded against in the bundled Netlify function).
Source:
app/api/cron/route.ts,src/lib/jobs/scheduled.ts,src/lib/pipeline/worker.ts,src/lib/render/render-creatives.ts,src/lib/video/render-videos.ts,src/lib/nurture/runner.ts