---
module: vault.cit-visits
title: CIT Visits
tab: Internal
order: 20
audience: internal
---

Ops/support view for the CIT Visits screen (`vault.bcos.cloud`). Served by **hiveops-vault** (Spring Boot, port 8094; NGINX maps `api.bcos.cloud/vault/` → `hiveiq-vault:8094/api/vault/`).

## Who can do what (roles)

There is **no role gating** in this module. `SecurityConfig` requires only `anyRequest().authenticated()` — any valid JWT gets in. There is **no** `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')` anywhere in vault. Access is controlled purely by **institution scoping**: every query filters on the caller's `institutionKey` (from the JWT claim).

- **Institution user** (JWT has `institutionKey`) → sees/edits only their own institution's visits, imports, ATMs, stats.
- **MSP admin** (JWT `institutionKey` is null) → on **create**, the institution is derived from the visited ATM's `device_summary` row. On **list ATMs** (`/atms`) a null institution returns `findAll()` (all devices). See the null-institution caveat below.

## Admin / operator actions

All driven from the visit detail panel and import screen — no separate admin console:

- **Confirm / Dispute a visit** — `PUT /api/vault/visits/{id}` with `{ "status": "CONFIRMED" | "DISPUTED" | "PENDING" }`. Buttons live in `VisitDetailPanel.svelte` (`setStatus`).
- **Edit notes / vendor / reference** — same `PUT`, body `{ notes, vendorName, referenceNumber }`.
- **Create a visit manually** — `POST /api/vault/visits` (201).
- **Delete a visit** — `DELETE /api/vault/visits/{id}` (204).
- **Bulk CSV import** — `POST /api/vault/imports` (multipart `file`); returns **202 Accepted** immediately, processes asynchronously.

## First things to check when it misbehaves

1. **Empty visit list / "no data"** — confirm the JWT actually carries `institutionKey`. Everything is institution-scoped; a token with the wrong or missing institution silently returns an empty page (no error).
2. **A device is missing from the ATM dropdown / `/atms`** — `device_summary` is a **read-only Kafka mirror**, not owned by vault. If a device is missing, it's an **upstream publish gap** from `hiveops-devices`, not a vault bug. Check the `hiveops.device.events` topic and the `DeviceEventConsumer` logs (`Upserted device_summary for {atmId}` / `has no deviceId, skipping insert`).
3. **CSV import stuck on PROCESSING** — the upload returns 202 and work runs on the `importExecutor` thread pool (`vault-import-*`). The client must **poll** `GET /api/vault/imports/{id}` for terminal `COMPLETED` / `FAILED`. If it never leaves PROCESSING, check vault logs for `Import {id} failed`.
4. **Import rejected at upload** — content-type must contain `csv` or `text`, and the file must be non-empty, else `400`. Odd browser content-types can trip this.
5. **`502` on the whole app** — vault CORS is disabled at Spring level (NGINX is the CORS authority). A CORS/`502` symptom is an NGINX/backend routing issue, not vault code.

## Common failure modes + fixes

| Symptom | Likely cause | Fix |
|---------|-------------|-----|
| Visits list empty for a real institution | JWT missing/wrong `institutionKey` | Re-issue token via auth; verify claim |
| Device absent from `/atms` list | Stale `device_summary` mirror | Have `hiveops-devices` re-publish `hiveops.device.events`; check consumer log |
| Import never completes | Async job errored | `GET /imports/{id}` → read `errorMessage`; check vault logs |
| Import 400 "Only CSV files are accepted" | Content-type not `csv`/`text` | Re-upload as `.csv` / `text/csv` |
| MSP admin sees no imports/stats | Null `institutionKey` queries (see caveat) | Query per-institution with a scoped token |

## Null-institution caveat (MSP admin)

`ImportController`, `StatsController`, and the visit list/recent queries pass `principal.institutionKey()` straight through. For an MSP admin whose token has **no** `institutionKey`, imports and stats are queried with `null` and will typically come back empty. Only **create-visit** and **`/atms`** have explicit null handling. If an MSP admin reports "no imports/stats," this is expected — use an institution-scoped token.

## Data (for support lookups)

Database **`hiveiq_vault`** (prod PostgreSQL on 10.10.10.188 via ProxyJump; dev = H2 in-memory). Tables: `cit_visits`, `cit_cassette_counts`, `cit_removals`, `cit_imports`, `device_summary`. All CIT rows carry `institution_key`.

Related: [vault.cit-visits] Overview (customer view) · [technical.vault] service overview.
