---
module: technical.devices
title: Devices — device management & source of truth for device data
tab: Overview
order: 10
audience: dev
---

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

## Responsibility

`hiveops-devices` (Spring Boot backend, `backend/`, artifact `hiveops-devices`, port **8096**) is the system of record for everything device/ATM-related: device inventory, groups, makes/models, custom field definitions, cassette configurations, agent tokens, agent profiles/scripts, per-institution agent properties, and config/hardware change tracking. It also serves the agent-facing internal endpoints (registration, heartbeat, config/hardware sync, log & module upload). It publishes device changes on Kafka so mirror consumers (incident/fleet/reports/recon) stay in sync. See [platform.service-ownership].

> Note: the repo `CLAUDE.md` is stale on this point — it claims "no dedicated devices microservice / calls hiveops-incident." The backend under `backend/` is real and owns these domains. The frontend (port 5177, `devices.bcos.cloud`) is the SPA layer.

## Data

- **Database:** `hiveiq_devices` (`spring.datasource.url` default `jdbc:postgresql://localhost:5432/hiveiq_devices`, overridable via env **`DB_URL`**). `ddl-auto=validate`; schema managed by **Flyway** (`V1`–`V16`). See [platform.data-stores].
- **Key tables / entities:**

| Table | Entity | Purpose |
|-------|--------|---------|
| `atms` | `Atm` | Core device inventory (source of truth) |
| `atm_properties` | `AtmProperties` | Per-device properties |
| `atm_groups` | `AtmGroup` | Device groupings |
| `atm_make` / `atm_model` | `AtmMake` / `AtmModel` | Make/model catalog |
| `atm_field_definitions` | `AtmFieldDefinition` | Custom field defs |
| `atm_config_changes` / `atm_hardware_changes` | `AtmConfigChange` / `AtmHardwareChange` | Config/hardware change audit |
| `atm_module_status` | `AtmModuleStatus` | Per-device module state |
| `atm_log_collects` | `AtmLogCollect` | Log collection records |
| `cassette_configurations` / `cassette_slots` | `CassetteConfiguration` / `CassetteSlot` | Cassette templates |
| `device_cassette_config` | `DeviceCassetteConfig` | Per-device cassette config |
| `agent_tokens` | `AgentToken` | Agent auth tokens |
| `agent_profiles` | `AgentProfile` | Agent config profiles |
| `agent_scripts` | `AgentScript` | Pushable agent scripts |
| `institution_keys` | `InstitutionKey` | Institution key registry |
| `institution_agent_properties` | `InstitutionAgentProperties` | Per-institution agent props |
| `institution_config_audit` | `InstitutionConfigAudit` | Institution config audit trail |
| `device_fleet_tasks` | `DeviceFleetTask` | Fleet task mirror for devices |
| `atm_service_schedule` | `AtmServiceSchedule` | Service schedule |

## Kafka

Bootstrap: `KAFKA_BOOTSTRAP_SERVERS` (default `kafka:29092`). Trusted deserializer packages: `com.hiveops.devices.kafka`, `com.hiveops.journal.kafka`. See [platform.kafka].

**Produced:**

| Topic | Producer class | Notes |
|-------|----------------|-------|
| `hiveops.device.events` | `DeviceEventPublisher` (constant `KafkaProducerConfig.DEVICE_EVENTS_TOPIC`) | keyed by atmId; `DeviceEventType` = `DEVICE_CREATED`, `DEVICE_UPDATED`, `DEVICE_DELETED` |

**Consumed:**

| Topic | Listener class |
|-------|----------------|
| `hiveops.fleet.task.events` (`FleetTaskKafkaConfig.TOPIC`) | `FleetTaskEventConsumer` |
| `journal.replenishments` (`ReplenishmentKafkaConfig.TOPIC`) | `ReplenishmentConsumer` |

## API surface

Admin controllers require JWT with role `MSP_ADMIN` or `BCOS_ADMIN` on mutating endpoints. Many device paths carry a Phase-2 `/api/devices*` alias alongside the legacy `/api/atms*` path.

Base paths: `/api/atms` + `/api/devices`, `/api/atm-groups`, `/api/atm-makes`, `/api/atm-models`, `/api/atm-field-definitions`, `/api/cassette-configurations`, `/api/institution-keys`, `/api/agent-tokens`, `/api/agent-profiles`, `/api/fleet/scripts`, `/api/internal/*`.

| Method + Path | Purpose |
|---------------|---------|
| GET `/api/atms` (alias `/api/devices`) | List devices |
| GET `/api/atms/paginated` | Paginated/filterable device list |
| GET `/api/atms/{id}` | Device detail |
| GET `/api/atms/dashboard/stats` | Fleet dashboard stats |
| GET `/api/atms/summary` | Device summary |
| POST `/api/atms` | Create device (admin) |
| PATCH `/api/atms/{id}/institution` | Reassign institution (admin) |
| POST `/api/atms/config/sync` · `/api/atms/hardware/sync` | Config/hardware sync ingest |
| GET `/api/atm-groups` · POST `/api/atm-groups/{id}/atms/{atmId}` | Groups + membership |
| GET/POST `/api/cassette-configurations` | Cassette templates |
| GET `/api/devices/cassette-config` | Resolve device cassette config |
| GET/POST `/api/institution-keys` · GET `/api/institution-keys/{key}/agent-properties` | Institution keys + agent props |
| GET/POST `/api/agent-tokens` · POST `/api/agent-tokens/{id}/revoke` | Agent token lifecycle |
| GET/POST/PUT/DELETE `/api/agent-profiles` · GET `/api/agent-profiles/modules-manifest` | Agent profiles + CDN modules manifest proxy |
| POST `/api/internal/atms/register` | Agent auto-registration (INTERNAL) |
| POST `/api/internal/atms/agent/{country}/{atm}/heartbeat` | Agent heartbeat (INTERNAL) |
| POST `/api/internal/agent-tokens/validate` | Token validation (INTERNAL) |

## Gotchas

- **Stale CLAUDE.md.** `hiveops-devices/CLAUDE.md` says the app has no backend and calls `hiveops-incident`; that's outdated — the `backend/` service exists and owns these domains.
- **DB uses `DB_URL`** (full JDBC URL) via `spring.datasource.url=${DB_URL:...}`, not split host/port/name vars.
- **Two auth realms.** Admin controllers use `@PreAuthorize("hasAnyRole('MSP_ADMIN','BCOS_ADMIN')")`; internal/agent controllers (`/api/internal/*`) use `hasRole('INTERNAL')` (e.g. `InternalAtmController`, `AgentTokenInternalController`, `InternalLogController`).
- **Phase-2 path aliases.** `/api/atms`, `/api/atm-groups`, `/api/atm-makes`, `/api/atm-models`, `/api/atm-field-definitions` each also respond on a `/api/device*` alias for the ongoing ATM→device rename; both must stay routable in nginx.
- **CDN token.** `agent.cdn.token` (env `CDN_TOKEN`) has a hardcoded fallback in `application.properties` used by the `/api/agent-profiles/modules-manifest` proxy.
- **Publish-on-every-change.** Device mirrors in incident/fleet/reports/recon depend on `hiveops.device.events`; any write path that skips `DeviceEventPublisher` leaves those mirrors stale (no reconciler).
- **Error message hiding.** `server.error.include-message=never` and `management.endpoint.health.show-details=never` — expect terse errors in prod.
