---
module: technical.reports
title: hiveops-reports — Custom report builder, scheduling & distribution
tab: Overview
order: 10
audience: dev
---

> **DRAFT · internal.** Written 2026-07-01 from hiveops-reports source. Verify against code before relying on specifics.

## Responsibility

Spring Boot microservice that owns the **custom report builder**: users define templates (data source + columns + filters + optional groupBy), runs execute asynchronously against other services' APIs, and results export as JSON preview or CSV. Also handles **cron scheduling** and **in-app distribution** of completed reports via hiveops-messaging. It does not own the underlying fleet data — it fetches from incident, journal, and fleet services at run time. See [platform.service-ownership].

## Data

- **DB name:** `hiveiq_reports` (PostgreSQL prod; H2 in-memory `dev` profile). Connection built from split `DB_HOST` / `DB_PORT` / `DB_NAME` / `DB_USERNAME` / `DB_PASSWORD` env vars (not `DB_URL`). Flyway migrations in `src/main/resources/db/migration/`. See [platform.data-stores].
- **Key tables** (from V1/V3/V8 migrations):

| Table | Purpose |
|---|---|
| `report_templates` | User-defined report definition (data source, columns, filters, schedule cron, distribution config) |
| `report_runs` | Execution instance — status PENDING → RUNNING → COMPLETED/FAILED, row count, CSV path |
| `report_distribution_log` | Per-recipient delivery record |
| `report_queries` | Named saved queries attached to a template (V3) |
| `device_summary` | Local mirror of device data kept in sync from Kafka (V8), used by `/api/devices/atms` |

## Kafka

See [platform.kafka].

**Produced**

| Topic | Producer class | Notes |
|---|---|---|
| `hiveops.reports.completed` | `kafka.ReportCompletedEventProducer` (constant in `ReportsKafkaConfig`) | Published from `ReportDistributorService`; message key = `institutionKey` or `"global"`; 3 partitions |

**Consumed**

| Topic | Listener class | Notes |
|---|---|---|
| `hiveops.device.events` | `kafka.DeviceEventConsumer` (constant in `DeviceEventKafkaConfig`) | Group `hiveops-reports-device-sync`, offset reset `earliest`; upserts/deletes `device_summary`. Handles `DEVICE_DELETED` event type; defaults null `institutionKey` to `"BCOS"` |

## API surface

Base paths under `/api/reports/*` (plus `/api/devices`, `/api/users`). All require a JWT Bearer token.

| Method | Path | Purpose |
|---|---|---|
| GET | `/api/reports/schema` | All data-source schemas (fields + supported filters) |
| GET | `/api/reports/schema/{dataSource}` | Single data-source schema |
| GET | `/api/reports/templates` | List caller's templates |
| POST | `/api/reports/templates` | Create a template |
| PUT | `/api/reports/templates/{id}` | Update a template |
| POST | `/api/reports/templates/{id}/run` | Trigger async run (returns run id) |
| GET | `/api/reports/templates/shared` | List templates shared with caller |
| POST | `/api/reports/templates/{id}/share` | Share a template |
| GET | `/api/reports/templates/{templateId}/queries` | List saved queries for a template |
| POST | `/api/reports/templates/{templateId}/queries/{queryId}/set-scheduled` | Mark a query as the scheduled one |
| GET | `/api/reports/runs` | List runs (pageable) |
| GET | `/api/reports/runs/{id}/preview` | JSON preview (first rows) |
| GET | `/api/reports/runs/{id}/download` | Download CSV |
| POST | `/api/reports/runs/{id}/distribute` | Manually distribute a completed run |
| GET | `/api/devices/atms` | List devices from local `device_summary` mirror |
| GET | `/api/users/me` | Current principal info |

Data sources (from `SchemaRegistry`, fetchers): `INCIDENTS`, `ATM_HEALTH`, `TRANSACTIONS`, `FLEET_TASKS`, `AGENT_VERSIONS`, `AGENT_MODULES` — each fetches from incident/journal/fleet APIs at run time.

## Gotchas

- **Scheduled runs use a self-minted JWT.** `InternalTokenGenerator` issues a short-lived ADMIN token signed with `JWT_SECRET` via `Keys.hmacShaKeyFor(secret.getBytes(UTF_8))` (same scheme as validation). User-triggered runs instead forward the caller's `Authorization` header to downstream services.
- **`/api/devices/atms`** is guarded by `@PreAuthorize("hasAnyRole('USER','CUSTOMER','ADMIN','MSP_ADMIN','BCOS_ADMIN')")` — broader than the typical `MSP_ADMIN`/`BCOS_ADMIN`-only pattern; other endpoints rely on the JWT filter rather than method-level `@PreAuthorize`.
- **`device_summary` is a Kafka-fed mirror** with no reconciler — if `hiveops.device.events` stops flowing, device data in reports goes stale (see the platform-wide device_summary mirror note).
- **Reports fetches, doesn't store, fleet data.** A report run makes live paginated HTTP calls to incident/journal/fleet; downstream outages surface as failed runs, not empty data.
- **CSV files** are written under `REPORT_UPLOAD_DIR` (default `/app/uploads/reports`; bind-mounted to `/data/hiveiq/reports/` in OpenMetal), not stored in the DB.
- **Ports:** internal `8088` (`PORT` env), external `8011` in OpenMetal; NGINX route `api.bcos.cloud/reports/` → `hiveiq-reports:8088/api/reports/`.
- **Async pool** (`AsyncConfig`) is small (core=2, max=5); the scheduler polls due templates on a fixed delay.
