--- module: technical.devices title: Devices — device management & source of truth for device data tab: Overview order: 10 audience: internal --- > **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.