API reference
Monitoring cron
Continuous monitoring depends on an external scheduler calling the cron endpoints on a regular cadence. Every endpoint is authenticated with the same CRON_SECRET bearer token, so you can drive them from cron-job.org, GitHub Actions, or any scheduler that can send an authenticated HTTP request.
Work is split into buckets by how time-sensitive it is. `/api/cron/uptime` is the one that matters most: it must be called every 60 seconds, and each invocation samples twice — immediately and 30 seconds later — which is what makes a 30-second check frequency achievable. Every job inside every bucket checks its own due-time, so calling more often never double-runs work.
Endpoints
The time-sensitive lane and the one required job. Uptime Monitoring, active health sessions, custom checks, and threat detection. POST is also accepted.
Legacy combined entry point, superseded by the split buckets. POST is also accepted for manual or legacy scheduler compatibility.
Daily Google Search Console rank-data sync for the SEO Suite Rank Tracker.
GET /api/cron/uptime
Authorization: Bearer <CRON_SECRET>
A successful call returns 202 Accepted immediately and completes the work in the background. Returns 401 when the bearer token does not match CRON_SECRET, and 503 when CRON_SECRET or SUPABASE_SERVICE_ROLE_KEY is unset.
Required environment variables
| Variable | Purpose |
| --- | --- |
| `CRON_SECRET` | Bearer token your scheduler sends; must match server env |
| `SUPABASE_SERVICE_ROLE_KEY` | Server-side reads and writes for samples, alerts, and activity |
| `RESEND_API_KEY` + branded sender | Email alert delivery when email channels are configured |
Optional alert channels (Slack, Discord, PagerDuty, OpsGenie, Teams, webhooks) use credentials saved per workspace in encrypted alert channel configs — not additional cron env vars.
Setting up cron-job.org
cron-job.org can run as many cron jobs as you want, so you can schedule each endpoint independently. For each job:
Create a cron job pointing at the full URL, for example https://your-app.com/api/cron/uptime.
Add the auth header. Under Advanced → Headers, add Authorization with value Bearer YOUR_CRON_SECRET (the same value as the server's CRON_SECRET).
Set the schedule per the table below.
Save and run once to confirm a 202 response. A 401 means the header/secret is wrong; 503 means a server env var is missing.
Recommended jobs
| Job | URL | Schedule | Why |
| --- | --- | --- | --- |
| **Uptime (required)** | `/api/cron/uptime` | **every 1 minute** (`* * * * *`) | The time-sensitive lane. Drives Uptime Monitoring, active health sessions, custom checks, and the threat sweeps. This is the job that sets your achievable check frequency |
| Workers | `/api/cron/jobs` | every 5 minutes (`*/5 * * * *`) | Executes Production Twin runs, site audits, evidence exports, and SEO Autopilot |
| Journeys | `/api/cron/journeys` | every 5 minutes (`*/5 * * * *`) | Runs scheduled and deploy-triggered journeys, and reclaims abandoned browser runs |
| Provider probes | `/api/cron/probes` | hourly (`17 * * * *`) | Keeps provider integration evidence fresh for the deploy gate |
| GSC rank sync | `/api/cron/gsc-sync` | daily (`0 3 * * *`) | Pulls the last 28 days of Search Console performance |
A scheduler tick is the floor on how often PreFlight can sample. Each `/api/cron/uptime` invocation runs the Sentinel sweep **twice** — once immediately and once 30 seconds later — so a one-minute schedule delivers the 30-second cadence sold on the Max plan. Both passes are anchored to the start of the invocation, so a slow tick never collapses them into one. Monitors set to 60 seconds or slower are simply not due on the second pass, so slower plans cost nothing extra. If your scheduler runs every 5 minutes instead, every monitor is effectively capped at 5 minutes no matter what frequency is selected in the dashboard. The Uptime page compares configured against observed cadence and warns you when they diverge.
The repository's own GitHub Actions workflow also calls `/api/cron/uptime` every 15 minutes. That exists so a deployment whose external scheduler was never configured still collects evidence instead of showing monitors that are permanently "due". It cannot deliver a 30-second or 60-second cadence — GitHub Actions cron is best-effort and coarse — so the one-minute external job above is still required for the cadence your plan advertises. The Uptime page tells you which situation you are in: when the sweep is overdue or has never reported, it says so at the top of the page rather than leaving every monitor claiming a next check that will not arrive.
`/api/cron/monitoring` is the original do-everything endpoint, kept for existing schedules. It is superseded by the split buckets above; running both is safe (every job self-gates on its own due-time) but unnecessary.
Every job self-throttles on its own due-time (Uptime Monitoring next-sample time, audit interval, revenue cadence, and so on), so an extra call never double-runs work — it only keeps things current. Over-calling costs nothing; under-calling silently delays detection.
What each monitoring tick does
Monitoring sessions. Loads active 24-hour sessions, records health samples, completes expired sessions, and triggers alerts after repeated failures.
Sentinel. Samples due Uptime Monitoring projects up to the batch limit, records outcomes, schedules next sample times, and emits threshold alerts.
Payment Sync. Runs due Stripe-to-Supabase reconciliation snapshots on the configured cadence.
Custom checks. Executes scheduled HTTP assertions defined in Custom Checks.
Vercel Watch. Probes connected Vercel projects for deployment and domain drift when integrations are due.
Scheduled audit categories. Runs any due AEO, Basic SEO, Performance, Accessibility, Domain, Email, passive Security, and Compliance audits. The unified Audit workspace presents the resulting evidence and one consolidated fix prompt.
Data Integrity Guard, Revenue Guard, and Usage & Cost Forecast. Captures due metadata baselines, runs due shadow-checkout guards, and recomputes usage/spend forecasts.
Activity and cost guards. Records operational events for Incidents and the Event Timeline; prunes cost logs and enforces sample rate limits.
Provider probes during monitoring
When a monitoring session or Uptime Monitoring sample runs provider probes, PreFlight uses a focused probe key set per integration — for example Stripe webhook registry, Supabase RLS audit, and Vercel deployment status — rather than the full check suite.
Response shape
The routes acknowledge and then work in the background, so the response confirms acceptance rather than results:
{ "ok": true, "queued": true, "bucket": "uptime" }
A 202 therefore means "accepted", not "succeeded". To see whether a tick actually completed, read the scheduler heartbeats: each run records its status, duration, and error into cron_heartbeats and appends to the cron_runs ledger. The Uptime page surfaces an overdue or failing uptime bucket directly, and the Event Timeline records the resulting monitoring activity.
Why not Vercel Cron
Vercel Cron cannot drive this workload. The Hobby plan allows two jobs at daily granularity, which cannot run a one-minute worker, and even on paid plans the minimum granularity is one minute per entry with no sub-minute option. PreFlight therefore ships with no crons array in vercel.json and expects an external scheduler.
If you deploy on Vercel, CRON_SECRET still belongs in the project environment variables — the cron routes authenticate the same way regardless of who calls them.
Self-hosted schedulers
Any scheduler that can send authenticated HTTP requests works:
- cron-job.org (see above)
- GitHub Actions on a cron schedule
- Cloudflare Workers Cron Triggers
- AWS EventBridge → Lambda
- systemd timer + curl
Requirements:
- Call
GET or POST on /api/cron/uptime (plus the other jobs in the table above).
- Send
Authorization: Bearer <CRON_SECRET>.
- Run the uptime job every 60 seconds. Anything slower becomes the hard floor on every monitor's check frequency.
- Allow the request to return immediately. The routes respond
202 and finish the work in the background, so a scheduler timeout of 30 seconds is plenty — do not configure your scheduler to wait for completion.
Rate limiting
Each cron route enforces 120 requests per minute per origin, in its own bucket (cron_uptime, cron_monitoring, and so on). A one-minute uptime schedule uses 1 of those 120, so normal scheduling stays far below the limit.
Troubleshooting
| Symptom | Check |
| --- | --- |
| 401 Unauthorized | `CRON_SECRET` mismatch between scheduler and server env |
| 503 Service unavailable | Missing `CRON_SECRET` or `SUPABASE_SERVICE_ROLE_KEY` |
| Uptime Monitoring samples stop | Project paused, cadence unset, or cron not firing |
| Samples arrive slower than the selected frequency | The uptime job is scheduled slower than every 60 seconds. The Uptime page shows configured vs observed cadence |
| "Uptime sweep is overdue" on the Uptime page | The uptime job has not reported inside two ticks plus a five-minute cushion. Check the scheduler's own execution log first |
| Payment Sync never runs | Stripe/Supabase not connected or mapping incomplete |
| Alerts not delivered | Alert channel disabled, rate limited, or active silence window |
<RelatedLinks
links={[
{ href: "/docs/guides/monitoring-and-sentinel", title: "Uptime Monitoring", description: "What the cron keeps alive after checks pass." },
{ href: "/docs/guides/revenue-watch", title: "Payment Sync", description: "Scheduled reconciliation behavior." },
{ href: "/docs/guides/deploy-gates", title: "Release Rules", description: "Gate blockers from stale Uptime Monitoring or revenue snapshots." },
{ href: "/docs/api/authentication", title: "API authentication", description: "Separate from cron auth — uses pf_live_ keys." },
]}
/>