---
module: devices.device-list
title: Device List
tab: Claude
order: 40
audience: dev
---

Terse agent reference. Frontend: `hiveops-devices/frontend/src/components/AtmManagement/AtmList.svelte`.

## Owner
- **hiveops-devices** backend (port 8096, DB `hiveiq_devices`) owns the grid data + all mutations.
- **hiveops-incident** owns ONLY `/api/geocode` (Lookup Coords).

## Endpoints (METHOD path → purpose · role)
| Method + Path | Purpose | Role |
|---|---|---|
| GET `/api/atms/paginated` | Grid rows (params: page,size,sort,search,status[],model[],agentStatus[],groupId,deviceType,agentVersion[]) | any auth, institution-scoped |
| GET `/api/atms/distinct-models` | Model filter options | any auth |
| GET `/api/atms/distinct-agent-versions` | Agent version filter options | any auth |
| GET `/api/atm-groups` | Group filter | any auth |
| GET `/api/institution-keys/accessible` | Add-Device institution dropdown | any auth |
| POST `/api/atms` | Add Device | `MSP_ADMIN`/`BCOS_ADMIN` |
| PATCH `/api/atms/{id}/institution` | Reassign institution | `MSP_ADMIN`/`BCOS_ADMIN` |
| GET `/api/geocode?address=` | Lookup Coords (**incident**) | any auth |

- Controller: `AtmController` `@RequestMapping({"/api/atms","/api/devices"})` → all paths also work under `/api/devices*`.

## Tables
- `atms` (source of truth), `atm_properties` (`lastHeartbeat`, agent version), `atm_groups`.

## Kafka
- Produces `hiveops.device.events` (`DeviceEventPublisher`; `DEVICE_CREATED/UPDATED/DELETED`, key=atmId) on every write. Consumed by incident/fleet/reports/recon mirrors. **No reconciler.**

## Gotchas
- Connection status = heartbeat age: `null`→`NEVER_CONNECTED`, ≤15min→`CONNECTED`, >15min→`DISCONNECTED` (`calculateConnectionStatus`). NOTE: customer overview.md still says "5 min" — code uses **15**.
- Status badge is derived **client-side**: `AGENT_OFFLINE` if disconnected/never-connected overrides real `serviceState`; then `IN_SUPERVISOR`; else `serviceState||status`. Status column sorts on backend `effectiveStatus`.
- Grid filtering/sorting/paging is **server-side** (re-queries on every change). `AGENT OFFLINE` status-filter option is translated to `agentStatus=disconnected,never_connected` client-side.
- Institution scoping via `InstitutionContext.resolveScope()`: `ROLE_CUSTOMER`/`ROLE_MSP_ADMIN` scoped (keys from JWT credentials); other roles (BCOS_ADMIN) unscoped. Empty list for one user = scoping, not a bug.
- `/api/geocode` is a relative `fetch` → depends on nginx routing to incident, not devices.
- Backend: `server.error.include-message=never` → terse prod errors; read logs (Loki).
- Devices backend config uses `DB_URL` (full JDBC URL).

## Do NOT
- Do NOT add device read/write endpoints to incident/fleet/etc — device domain is owned by **hiveops-devices** only.
- Do NOT assume `AGENT OFFLINE` means a service fault — it means no heartbeat in 15 min.
- Do NOT expect other apps to reflect a device edit unless `hiveops.device.events` was published (no reconciler); use `POST /api/atms/republish-all` (admin) to rebuild mirrors.
- Do NOT look for geocode in hiveops-devices — it lives in hiveops-incident.
- Do NOT hit `POST /api/atms` as a non-admin (403).
- Do NOT invent split DB host/port env vars — it's `DB_URL`.
