Scheduled runs require the cron to be wired to hit
/api/cron/tick.
During pilot / initial deployment this is not wired to AWS EventBridge by
default, so schedules are defined but will not fire automatically.
They’ll still be created, persisted, and display nextRunAt — but that
timestamp will not trigger anything until a scheduler invokes the tick.To deliver a report on demand during pilot, use the Run Now button in
/settings/reports or the
POST /api/v1/reports/templates/[id]/run-now API. Run Now works
unconditionally and does not depend on the cron being wired.To enable automatic scheduled firing, see
EventBridge Setup — optional during
pilot./api/cron/tick — the same
endpoint that evaluates alert rules.
Schema
Cron semantics
Standard 5-field cron (minute hour day-of-month month day-of-week),
parsed by cron-parser.
Examples:
*/5 * * * *— every five minutes0 8 * * *— daily at 08:000 9 * * 1-5— weekdays at 09:000 9 1 * *— first of every month at 09:00
UTC. Any IANA name (America/New_York,
Europe/London, …) is accepted.
nextRunAt computation
On create and on every PATCH that modifies cronExpression or
timezone, the server recomputes nextRunAt using cron-parser.
On every cron tick that fires the schedule, nextRunAt advances to the
next occurrence relative to the current tick time — anchoring off
now prevents drift after restarts and missed ticks.
Invalid cron expressions park the schedule at nextRunAt = null. It
will not fire until an operator fixes the expression via the settings
UI or the PATCH endpoint.
destinations
Array of notification provider UUIDs. Each provider is looked up from
the encrypted PlatformSetting vault; its kind determines the
payload shape:
- ops_email — subject includes the template name; body = executive summary + key takeaways; artifacts attached as files.
- slack / teams — narrative + bullets + deep link to
/api/v1/reports/runs/[id]. No attachments. - webhook — POST body includes artifact base64 inline (under
extraPayload.artifacts[]) so downstream consumers receive the content without a follow-up GET.
lastStatus
Possible values after a cron tick:
completed— run finished successfully; every destination delivered.completed_with_errors— run finished, but at least one destination delivery failed.failed— runner threw or returned a fatal error (typically a template-not-found condition).
lastStatus stays at its default
(pending) and lastRunAt stays null.
Tick behaviour
Per tenant, the tick:prisma.reportSchedule.findMany({ tenantId, enabled: true, OR: [{ nextRunAt: null }, { nextRunAt: { lte: now } }] })- For each due schedule, invoke the runner with
triggeredBy: "cron",dispatch: true,persistRun: true. - Update
lastRunAt,nextRunAt,lastStatuson the schedule. - A single schedule failing does not block the rest; an alert-evaluator failure does not block report processing.
Triggering a run without the cron
Two unconditional options work during pilot (or any time you want immediate delivery):- Run Now button —
/settings/reports→ pick a template → Run Now. This fires the runner immediately and creates aReportRun. - Run Now API —
POST /api/v1/reports/templates/[id]/run-nowwith tenant context. Same runner path; returns the run ID.
CRON_TOKEN to be
set or EventBridge to be wired.
Manual cron tick
For parity with scheduled behaviour (evaluate every due schedule in one call), hit the cron directly:CRON_TOKEN to be set on the service. Any schedule whose
nextRunAt <= now fires during that tick. Useful for development and
for validating a schedule end-to-end before enabling EventBridge.
API
See Reports API reference for CRUD routes (POST /api/v1/reports/schedules, PATCH /api/v1/reports/schedules/:id,
etc.).