---
module: technical.claims
title: hiveops-claims — ATM cash-shortage loss recovery service
tab: Overview
order: 10
audience: dev
---

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

## Responsibility

Spring Boot backend (port 8092, image `hiveiq-claims`) that tracks ATM cash-shortage loss-recovery **claims** and their multi-party payment splits between the institution, the CIT (cash-in-transit) vendor, and the SP (service provider). Owns claims, vendors, claim messages/notes/attachments, payment splits, and per-institution email templates. All claim/vendor data is scoped by `institutionKey` from the JWT principal. Frontend is a separate Svelte SPA (`claims.bcos.cloud`, image `hiveiq-claims-frontend`). See [platform.service-ownership].

## Data

- **Database:** `hiveiq_claims` (PostgreSQL, `prod` profile). `dev` profile uses H2 in-memory (`create-drop`, Flyway disabled). See [platform.data-stores].
- **Connection env vars:** split form `DB_HOST` / `DB_PORT` (default 5432) / `DB_NAME` (default `hiveiq_claims`) / `DB_USERNAME` / `DB_PASSWORD` — note: NOT the single `DB_URL` form used by hiveops-journal.
- **Key tables** (Flyway `db/migration`, V1–V11):

| Table | Purpose |
|-------|---------|
| `claims` | Shortage claim per terminal — shortage amount, loss period, status, claim type, CIT/SP vendor FKs, `submitted_at` |
| `claim_payments` | Per-party owed amount (`INSTITUTION` / `CIT` / `SP`), one row per involved party |
| `claim_messages` | Threaded institution↔vendor messages (V3 adds additional email) |
| `claim_notes` | Internal institution-only notes, never shown to vendors |
| `claim_attachments` | Uploaded documents (stored under `uploads.dir`, default `/tmp/claims-uploads`) |
| `vendors` | CIT/SP vendor registry, scoped by `institutionKey` |
| `email_templates` | Per-institution email templates (V2) |
| `device_summary` | Read-model mirror of devices, kept in sync via Kafka (V9/V11) |

Claim status enum (entity `Claim.ClaimStatus`, updated by V5/V8): `OPEN`, `WAITING_CIT`, `WAITING_SP`, `WAITING_INSTITUTION`, `PENDING_PAYMENT`, `RESOLVED_PAID`, `RESOLVED_OFFAGE_FOUND`, `RESOLVED_DENIED`. (The `OPEN→REVIEW→PENDING→RESOLVED` set in CLAUDE.md is stale.)

## Kafka

- **Consumed:** `hiveops.device.events` — `DeviceEventConsumer` (`@KafkaListener`, group `hiveops-claims-device-sync`, factory `deviceEventKafkaListenerContainerFactory`; topic/group constants in `DeviceEventKafkaConfig`). Upserts/deletes rows in `device_summary`; `DEVICE_DELETED` events remove the record; missing `institutionKey` defaults to `"BCOS"`.
- **Produced:** None.

See [platform.kafka]. This service is one of the `device_summary` mirror consumers (incident/fleet/reports/recon/claims).

## API surface

All endpoints require a JWT Bearer token except health/swagger/h2-console/error and `/api/internal/**` (see Gotchas). Base path `/api/claims`; externally routed as `api.bcos.cloud/claims/`.

| METHOD | Path | Purpose |
|--------|------|---------|
| GET | `/api/claims` | List claims (filter `?status=`, `?search=`) |
| GET | `/api/claims/stats` | Dashboard stats — exposure, recovered YTD, totals |
| POST | `/api/claims` | File claim — creates split payments across involved parties |
| GET | `/api/claims/{uuid}` | Claim detail with payments |
| PUT | `/api/claims/{uuid}` | Update status / claim type / assign-clear CIT & SP vendors |
| GET | `/api/claims/atms` | ATM list (from `device_summary`) for claim creation |
| GET/POST | `/api/claims/vendors` | List (`?type=CIT`/`SP`) / register vendors |
| GET/POST | `/api/claims/{claimUuid}/messages` | Vendor thread (`?vendorUuid=` filter) / send message |
| GET/POST | `/api/claims/{claimUuid}/notes` | Internal notes |
| GET/POST | `/api/claims/{claimUuid}/attachments` | List / upload attachments |
| GET | `/api/claims/{claimUuid}/attachments/{attachmentUuid}/download` | Download an attachment |
| GET | `/api/claims/{claimUuid}/payments` | Payment breakdown by party |
| PUT | `/api/claims/{claimUuid}/payments/{party}` | Update one party's owed amount (`party`: INSTITUTION/CIT/SP) |
| GET/POST/PUT/DELETE | `/api/claims/settings/email-templates` | Manage per-institution email templates (`/effective` = resolved) |
| GET | `/api/users/me` | Current principal info |
| GET/POST/PUT/DELETE | `/api/internal/email-templates` | Internal template management (secret-gated) |

## Gotchas

- **No `hasAnyRole` checks.** Security is JWT-authentication-only (`SecurityConfig` + `JwtAuthenticationFilter`); every authenticated user passes `anyRequest().authenticated()`. Data isolation comes from `institutionKey` scoping in services, not role guards — unlike most HiveOps services that use `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')`.
- **`/api/internal/**` is `permitAll()` at the security layer** but gated inside `InternalEmailTemplateController` by an `X-Internal-Secret` header compared against `INTERNAL_SERVICE_SECRET` (default empty).
- **Split is NOT always a flat 3-way.** `createSplitPayments` always adds `INSTITUTION`, and adds `CIT`/`SP` only when a vendor of that type is assigned; the shortage is divided equally with `RoundingMode.FLOOR` and the remainder assigned to the last party. Later CIT/SP vendor assignment via `PUT /{uuid}` creates that party's payment row at `amountOwed = 0` (not re-split). (CLAUDE.md's "auto-created as equal thirds" is only true when both vendors are set at filing.)
- **Dev profile is H2** with `ddl-auto: create-drop` and Flyway disabled; prod uses `validate` + Flyway. Schema drift only surfaces in prod.
- **Attachment storage is a local dir** (`UPLOADS_DIR`, default `/tmp/claims-uploads`) — not object storage; multipart cap is 50MB file / 52MB request.
- **`device_summary` has no reconciler** — if the devices service fails to publish a `hiveops.device.events` change, the claims mirror goes stale silently.
