---
module: devices.device-list
title: Device List
tab: Internal
order: 20
audience: internal
---

Ops/support view of the Device List (frontend `AtmManagement/AtmList.svelte`, served by the **hiveops-devices** backend, port **8096**). This is the fleet inventory grid at `devices.bcos.cloud`.

## What powers each part
- **Grid rows / Total count** → `GET /api/atms/paginated` (`AtmController.getAllAtmsPaginated`). Read-only, no admin role required — but results are **institution-scoped** (see below).
- **Model filter dropdown** → `GET /api/atms/distinct-models`.
- **Agent Version filter dropdown** → `GET /api/atms/distinct-agent-versions`.
- **Group filter** → groups loaded via `GET /api/atm-groups`.
- **+ Add Device** → `POST /api/atms` — **admin only** (`hasAnyRole('MSP_ADMIN','BCOS_ADMIN')`).
- **📍 Lookup Coords** → `GET /api/geocode?address=...` — this is served by **hiveops-incident** (`GeocodingController`), NOT devices. Called as a relative `fetch('/api/geocode')`, so it depends on nginx routing.

## Roles required
- **Viewing the list, filters, distinct-models/versions:** any authenticated user; data is scoped by institution.
- **Add Device / any mutation** (`POST/PUT/PATCH/DELETE /api/atms*`): `MSP_ADMIN` or `BCOS_ADMIN`. A `CUSTOMER` gets 403 on Add Device.
- **Institution scoping** (`InstitutionContext.resolveScope`): callers with `ROLE_CUSTOMER` or `ROLE_MSP_ADMIN` are scoped to their institution key(s) (from JWT credentials); `BCOS_ADMIN` (and other unscoped roles) see the whole fleet.

## First things to check when it misbehaves
1. **List is empty / "No ATMs found" for one user only** → almost always institution scoping. Confirm the user's JWT institution keys; an MSP/customer only sees their own devices. Compare against a `BCOS_ADMIN` login (unscoped).
2. **Device shows "AGENT OFFLINE" unexpectedly** → the **Status** badge is overridden to `AGENT OFFLINE` whenever `agentConnectionStatus` is `DISCONNECTED` or `NEVER_CONNECTED`, regardless of the real service state. Connection status is derived from **last heartbeat**: `CONNECTED` ≤15 min, `DISCONNECTED` >15 min, `NEVER_CONNECTED` if never seen. So an offline badge = no heartbeat in 15 min, not a service fault. Check the device's actual `lastHeartbeat` / agent connectivity.
3. **Wrong institution stuck on a device** → institution is auto-derived from the agent token; a mis-imaged box keeps the wrong institution. Fix via `PATCH /api/atms/{id}/institution` (admin). Cross-check LOCATION to find the true owner.
4. **A device edit doesn't show up in Incident/Fleet/Reports/Recon** → those are **Kafka mirrors** of `hiveops.device.events`. If the devices backend can't reach Kafka, the mirrors go stale (there is no reconciler). Check devices-backend Kafka connectivity; a stale mirror is the usual cause of "wrong data in app X but right in Devices."
5. **Add Device fails silently / terse error** → devices backend runs `server.error.include-message=never`, so prod errors are terse. Check the container logs (Loki) rather than the UI message.
6. **"Lookup Coords" fails but everything else works** → geocode is a different service (incident) on a different nginx path. Verify `/api/geocode` routes to incident and that the upstream (US Census / geocoder) is reachable.
7. **Filters don't narrow the list** → the grid re-queries the backend on every filter change (server-side filtering). `AGENT OFFLINE` in the Status filter is translated client-side into an `agentStatus` filter (disconnected + never_connected), not a DB status value.

## Common failure modes + fixes
| Symptom | Likely cause | Fix |
|---------|-------------|-----|
| One user sees far fewer devices | Institution scoping via JWT | Expected; verify with BCOS_ADMIN |
| Status = AGENT OFFLINE | No heartbeat >15 min | Check agent/network, not service state |
| Add Device → 403 | Non-admin role | Needs MSP_ADMIN / BCOS_ADMIN |
| Edit not reflected in other apps | Kafka mirror stale | Check devices→Kafka; republish (`POST /api/atms/republish-all`, admin) |
| Lookup Coords broken | incident/geocode down or nginx | Check incident + `/api/geocode` route |
| Terse/empty error text | `include-message=never` | Read backend logs, not UI |
