---
module: reports.templates
title: Report Templates
tab: Claude
order: 40
audience: dev
---

> Terse act-without-guessing reference. Owner service: **hiveops-reports**. Nothing here is guessed — all confirmed in source.

## Ownership
- **Feature owned by `hiveops-reports`.** Template state never lives in another service.
- Data at run time is **fetched** (not owned) from incident/journal. Do not look for template rows in `hiveiq_incident`/`hiveiq_journal`.

## Routing / ports
- Internal: `hiveiq-reports:8088` · OpenMetal external: `8011`
- NGINX: `api.bcos.cloud/reports/` → `hiveiq-reports:8088/api/reports/`
- Frontend SPA: `report.bcos.cloud` (port `5182`), component `frontend/src/components/Reports.svelte`, client `frontend/src/lib/api.ts`

## Endpoints (METHOD path) — all require JWT Bearer
| Method | Path |
|---|---|
| GET | `/api/reports/templates` |
| POST | `/api/reports/templates` |
| GET | `/api/reports/templates/{id}` |
| PUT | `/api/reports/templates/{id}` |
| DELETE | `/api/reports/templates/{id}` |
| GET | `/api/reports/templates/shared` |
| POST | `/api/reports/templates/{id}/share` |
| DELETE | `/api/reports/templates/{id}/share` |
| POST | `/api/reports/templates/{id}/run` → `202` + run id |
| GET | `/api/reports/templates/{templateId}/queries` |
| POST | `/api/reports/templates/{templateId}/queries` |
| PUT | `/api/reports/templates/{templateId}/queries/{queryId}` |
| DELETE | `/api/reports/templates/{templateId}/queries/{queryId}` |
| POST | `/api/reports/templates/{templateId}/queries/{queryId}/set-scheduled` |
| DELETE | `/api/reports/templates/{templateId}/queries/scheduled` |
| GET | `/api/reports/schema` , `/api/reports/schema/{dataSource}` |

## Tables (DB `hiveiq_reports`)
- `report_templates` (jsonb: `columns`,`filters`,`group_by_fields`,`aggregations`,`distribution_config`; ownership `created_by_user_id`; `institution_key`, `shared`, `schedule_cron`, `next_run_at`, `schedule_query_id`)
- `report_queries` · `report_runs` · `report_distribution_log` · `device_summary`

## Kafka topics
- **Produced:** `hiveops.reports.completed` (on run complete/distribute; key = `institutionKey` or `"global"`)
- **Consumed:** `hiveops.device.events` (group `hiveops-reports-device-sync`, offset `earliest`) → `device_summary`
- Template **CRUD emits no Kafka**; only run/distribute does.

## Roles / auth
- Endpoints require only a valid JWT (`SecurityConfig` = `.authenticated()`). **No** `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')` gate on templates.
- Access control = **per-user ownership** (`created_by_user_id`), not role, not institution.
- User runs forward caller Bearer; scheduled runs mint ADMIN JWT via `InternalTokenGenerator` (shared `JWT_SECRET`, scoped to `institution_key`).
- Data sources (enum, validated by `SchemaRegistry`): `INCIDENTS`, `ATM_HEALTH`, `TRANSACTIONS`, `FLEET_TASKS`, `AGENT_VERSIONS`, `AGENT_MODULES`, `SOFTWARE_ERRORS`.

## Gotchas
- Setting `scheduleEnabled=true` sets `next_run_at = now()` → fires on next 60s scheduler tick regardless of cron.
- `schedule_cron` is **6-field Spring cron, evaluated UTC**. Invalid cron → `next_run_at` becomes null → schedule silently stops.
- `getForUser` is cached (`reportTemplates`, key `id:userId`); evicted on update/delete/share only.
- Shared templates listed by `findBySharedTrueAndCreatedByUserIdNot` — **no institution filter** → visible cross-institution.
- Only the **owner** can GET/PUT/DELETE a template; a recipient of a shared template can only `.../run` it (`triggerUserRun` = owner OR sharedTrue).
- Downstream (incident/journal) outage → run FAILED with `report_runs.error_message`, not an empty template.
- `V6` seeds a shared `SOFTWARE_ERRORS` template (Mon 08:00 UTC). Frontend TS `ReportDataSource` type omits `SOFTWARE_ERRORS` though backend supports it.

## Do NOT
- Do NOT add an endpoint/table for reports data in another service — hiveops-reports owns templates end-to-end.
- Do NOT assume an admin role gate — there is none on templates.
- Do NOT look for template CRUD Kafka events — none exist.
- Do NOT expect `institution_key` to scope visibility of **shared** templates (it does not).
- Do NOT trust cron in local time — it's UTC.
- Do NOT edit another user's template via API — ownership is enforced by `created_by_user_id`.
