Scheduled Reports
Scheduled reports render a dashboard on a recurring schedule (daily, weekly, or monthly) and deliver the result as CSV, PNG, or PDF over email, webhook, or both. The scheduler walks every enabled report once a minute and dispatches any whose next_run_at has come due.
dashboard_id. CSV reports export every widget on that dashboard as separate sheets in a single file; PNG and PDF reports render the dashboard as a composed visual artifact via headless Chrome server-side.Schedule kinds
| Kind | Required fields | Behavior |
|---|---|---|
daily | hour | Runs every day at hour:00 in the configured timezone. |
weekly | hour, day_of_week | Runs weekly at hour:00 on the given day (0=Sunday through 6=Saturday). |
monthly | hour, day_of_month | Runs monthly at hour:00 on the given day-of-month (1–31). Months that don't contain the chosen day are skipped without error. |
Timezones use IANA names (America/New_York, Europe/Berlin, UTC). DST transitions are respected — hour=9 in Europe/Berlin means 9 AM local, which moves relative to UTC twice a year. Invalid or empty timezone strings fall back to UTC.
Output formats
Creating a report
Create, update, and delete endpoints require an admin API key or admin-scoped session. Listing and viewing runs is open to any authenticated user.
POST /v1/reports
Authorization: Bearer YOUR_ADMIN_API_KEY
Content-Type: application/json
{
"dashboard_id": "dash_abc123",
"name": "Weekly usage summary",
"schedule_kind": "weekly",
"hour": 9,
"day_of_week": 1,
"timezone": "America/New_York",
"format": "pdf",
"email_to": ["cto@example.com", "ops@example.com"],
"webhook_url": "https://hooks.example.com/emban-reports"
}
The response returns the full report including a computed next_run_at. The scheduler uses that timestamp to decide when to dispatch.
Delivery
Reports send as a MIME multipart/mixed message: a short plain-text body (dashboard name, run timestamp, link back to the app) plus the artifact as an attachment with the correct Content-Type and filename. Multiple email_to recipients go in a single To: header.
Webhook
The webhook receives the raw artifact bytes as the POST body, with content type set to the artifact's MIME and three identification headers:
POST https://hooks.example.com/emban-reports
Content-Type: text/csv | image/png | application/pdf
X-Emban-Report-Id: 17
X-Emban-Report-Name: Weekly usage summary
X-Emban-Dashboard-Id: dash_abc123
<raw artifact bytes>
Unlike alert webhooks, report webhooks do not retry — they run with a 20-second timeout and a single attempt. The artifact is large and delivery is periodic, so the cost-benefit of retrying differs. If your receiver is flaky, terminate quickly so the next scheduled run is fresh.
Run history
Every dispatch writes a report_runs row with started_at, finished_at, status (running, success, failed), artifact_bytes, and any error message. This is the source of truth for "did Tuesday's report actually go out":
GET /v1/reports/17/runs
[
{
"id": 512,
"report_id": 17,
"started_at": "2026-04-21T13:00:01Z",
"finished_at": "2026-04-21T13:00:07Z",
"status": "success",
"artifact_bytes": 184320
},
{
"id": 503,
"report_id": 17,
"started_at": "2026-04-14T13:00:02Z",
"finished_at": "2026-04-14T13:00:19Z",
"status": "failed",
"error": "webhook returned 502"
}
]
Managing reports
# List reports
GET /v1/reports
# Pause/resume (preserves run history)
PATCH /v1/reports/17
{"enabled": false}
# Change time or recipients without recreating
PATCH /v1/reports/17
{"hour": 10, "email_to": ["cfo@example.com"]}
# Delete report (cascades report_runs)
DELETE /v1/reports/17
Design patterns
timezone: "America/New_York", not UTC. Wrong timezone is the most common "my report arrives at 4 AM" bug.
status=success on the last run is cheap insurance.
Observability
Webhook attempts are recorded in the shared webhook_deliveries table with source = "report", visible alongside alert deliveries under Admin → Webhooks. Email attempts are not currently surfaced in the admin UI — if email delivery is critical, track it server-side through your SMTP relay's bounce/delivery logs and cross-reference report_runs.