---
module: msp.institution-keys
title: Institution Keys
tab: Claude
order: 40
audience: dev
---

> Terse act-without-guessing reference. Confirmed against `hiveops-devices` + `hiveops-msp` code 2026-07-01.

## Owner
- Service: **hiveops-devices** (Spring Boot; NOT hiveops-msp — MSP SPA is frontend-only).
- DB: **hiveiq_devices**. Frontend view: `hiveops-msp/frontend/src/components/InstitutionKeys/InstitutionKeysView.svelte`.
- Controller: `InstitutionKeyController` `@RequestMapping("/api/institution-keys")`. Service: `InstitutionKeyService`.

## Endpoints (external `/devices/*` → NGINX → internal `/api/*`)
| Method + path | Auth | Handler |
|---------------|------|---------|
| GET `/api/institution-keys` | any JWT, scoped | `getAll` → `getAccessible` |
| GET `/api/institution-keys/accessible` | any JWT, scoped | `getAccessible` |
| POST `/api/institution-keys` | `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')` | `create` (body: `key`,`institutionName`,`supportedDeviceTypes`) |
| PUT `/api/institution-keys/{key}` | `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')` | `update` (body: `institutionName`,`supportedDeviceTypes`) |
| DELETE `/api/institution-keys/{key}` | `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')` | `delete` |
| GET/PUT `/api/institution-keys/{key}/agent-properties` | GET scoped / PUT MSP_ADMIN,BCOS_ADMIN | agent config |
| GET `/api/institution-keys/{key}/agent-properties/flat` | scoped | flat CONFIG_UPDATE map |
| GET `/api/institution-keys/config-status` | scoped | pending-push status |
| GET `/api/institution-keys/config-audit?institutionKey=&page=&size=` | scoped | audit page |
| POST `/api/internal/atms/register?deviceType=&institutionKey=&atmName=&country=` | agent-proxy internal | enforces device-type policy |

## Frontend calls (`institutionKeyAPI` in `hiveops-msp/frontend/src/lib/api.ts`)
- `getAll` → GET `/devices/institution-keys`
- `create` → POST `/devices/institution-keys`
- `update(key)` → PUT `/devices/institution-keys/${key}`
- `delete(key)` → DELETE `/devices/institution-keys/${key}`

## Tables (hiveiq_devices)
| Table | R/W | Key / notes |
|-------|-----|-------------|
| `institution_keys` | R/W | PK `key` VARCHAR(10); `institution_name` NOT NULL **UNIQUE**; `supported_device_types` VARCHAR(20) default `'ATM,TCR'`. Migration `V2__create_atm_core_tables.sql` |
| `institution_agent_properties` | R/W (via agent-properties) | PK `institution_key` FK **ON DELETE CASCADE** → `institution_keys(key)`. Migration `V3` |
| `institution_config_audit` | W (on agent-props save) | audit of config changes |
| `atms` | R (scope join) | `atms.institution` = resolved `institution_name` |

## Kafka
| Topic | Producer | Note |
|-------|----------|------|
| `hiveops.device.events` | `DeviceEventPublisher` (on register/device change) | mirrors device data downstream |
| — | none | **institution-key CRUD emits NO Kafka event** |

## Roles
- Read (`getAll`/`accessible`/`config-*`): no `@PreAuthorize`; scoped by `InstitutionContext.resolveScope()`. `null` (BCOS_ADMIN/ADMIN/QDS_ADMIN/USER) = all; list (CUSTOMER/MSP_ADMIN) = JWT keys.
- Write (create/update/delete/save-agent-props): `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')` — plain `ADMIN` is **excluded**.

## Gotchas
- `DeviceType` enum = `ATM, TCR, SRV`. `parseDeviceType` maps unknown/blank → **`ATM`** (silent fallback).
- Registration rejected (403 `{linked:false,error}`) only when key **is mapped** AND `supported_device_types` non-blank AND lacks the type. Unmapped key → no type check; `atms.institution` left blank → invisible to scoped users.
- POST with an **existing key overwrites** it (JPA `save` on `@Id`, no 409).
- `institution_name` is UNIQUE → duplicate name save fails at DB (5xx, not clean 400).
- Renaming `institutionName` does NOT update `atms.institution` on already-registered devices; scope resolves key→current name, so old-named devices stop matching until re-registered/renamed.
- DELETE cascades → wipes `institution_agent_properties` for that key.
- `key` is stored upper-cased/trimmed; frontend also `.toUpperCase()` on create. `key` immutable after create (PK).
- Single-key lookup cached (`institutionKeys` cache); `getAll` uncached.

## Do NOT
- Do NOT look for an institution-keys endpoint/table in **hiveops-msp** or **hiveops-mgmt** — it's **hiveops-devices** only.
- Do NOT expect a Kafka event or `device_summary` update after a key rename — none fires.
- Do NOT change `key` on a mapping — recreate; PK is immutable.
- Do NOT grant create/edit to a plain `ADMIN` role and expect it to work — needs MSP_ADMIN/BCOS_ADMIN.
- Do NOT delete a key to "clean up" — it cascades away its agent config and blinds JWT holders of that key.
- Do NOT treat unknown-`deviceType` reg failures as typos — the fallback is ATM; a 403 is a real type mismatch.
