Skip to Content
ConceptsReports

Reports

Available

  • Report schedules on flows (settings.reports), time and event modes
  • Email delivery of a 7-day link
  • Send now (POST /v1/flows/{id}/reports/send-now)
  • Per-flow history
  • Public payload endpoint (GET /reports/{token})
  • GUI report page (/report/{token}) with PDF download
  • comms adapter delivery
  • report.generated webhook events

A report is a frozen snapshot of one run’s per-target results (trigger state, colors, probability, and every tile’s values) stored by the Engine and shared by link. The Engine owns scheduling, generation, delivery and history; rendering is the GUI’s job: the emailed link opens the public report page (/report/{token}), which fetches the payload and draws it (HTML plus a Download PDF of the same frozen snapshot). Headless integrations can consume the payload JSON directly.

Configuration: settings.reports

Report config rides the flow definition as a validated block:

{ "reports": { "schedules": [{ "id": "morning", // stable per-schedule id (your choice, ≤64 chars) "name": "Morning digest", "enabled": true, "mode": "time", // "time" (calendar) or "event" (on trigger) "months": [], // 1–12; empty = every month "weeksOfMonth": [], // 1–4, 5 = "last week"; empty = every week "daysOfWeek": [1, 3, 5], // 0–6, Sunday = 0; empty = every day "timeOfDay": "08:00", // matched at hour granularity; default 08:00 "timeZone": "America/Denver", // IANA; default UTC "targetFilter": "all", // all | triggered | not_triggered "contactListIds": ["…"] }], "brand": { "name": "Acme Vineyards", "logo": "https://…" } // optional flow brand } }

The optional brand overrides the tenant brand per field for this flow’s reports and their emails; unset fields fall back to the tenant brand.

Modes

  • Time: an hourly Engine pass finds due schedules (calendar match in the schedule’s time zone), forces a fresh run of the flow, and sends the report when the run finishes. One send per hourly slot per schedule.
  • Event: fires at the end of a scheduled run in which any target triggered, throttled to one send per schedule per 6 hours. Manual runs never fire event reports.
  • Send now: POST /v1/flows/{id}/reports/send-now freezes a report from an existing succeeded run (the latest by default) and emails it to the given contact lists; the response carries the link immediately. No throttle.

Delivery

Email only. Recipients resolve from the schedule’s contact lists (de-duplicated); a contact is emailed only when they have an address, emailOptIn, and a verified address; everything else is recorded as skipped_optout / skipped_unverified in the history. The email subject is "{brand} Report: {flow}".

By default the Engine sends through the platform email service. A tenant can register a comms adapter, a sandboxed script (ctx, req) => ({ok, error?}) receiving {channel, to, subject?, message, html?}, to send through their own provider instead; it applies to alert delivery too. See Adapters.

Every report gets an opaque token (grt_…, stored hashed) valid for 7 days. The emailed link points at the GUI’s report page; the data behind it is public JSON:

GET /reports/{token} → { flowName, generatedAt, reportDate, notes, dataWindow, temperatureUnits, colors, brand: {name, logo}, flowTiles, targets: [{name, boundary?, triggered, probability, color, runAt, tiles: [...points...]}], expiresAt }

Unknown or expired tokens 404. Anyone with the link can read the payload until it expires, so treat report links like the shared documents they are.

History

GET /v1/flows/{id}/reports lists send attempts newest-first: schedule, mode (time | event | manual), status (sent | skipped | error), reason, target counts, and per-recipient outcomes. Event-mode sends suppressed by the 6-hour throttle are silent (no history row).