---
module: vault.import
title: Import
tab: Internal
order: 20
audience: internal
---

Ops/support view of the CIT CSV import screen. Backend is **`hiveops-vault`** (port 8094, `hiveiq_vault` DB). Endpoints live under `/api/vault/imports`, reached in prod via `api.bcos.cloud/vault/imports`.

## Who can use it

- **No role gating.** `SecurityConfig` only requires `authenticated()` on `anyRequest()` — there is **no** `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')` check. Any user with a valid JWT can upload and list imports.
- Access is **institution-scoped**, not role-scoped: list/detail/upload all use `principal.institutionKey()`. A user only sees imports (and creates visits) for their own institution.
- `importedBy` is stamped from the JWT **email** (`principal.email()`), shown in the **By** column.

## First things to check when it misbehaves

1. **Import stuck on "Processing" forever** — the frontend polls `GET /imports/{id}` every 2s and only stops on `COMPLETED`/`FAILED`. If it never flips, the `@Async("importExecutor")` job died or the pool is starved. Check vault logs for `Import {id} failed` / `Import record {id} not found`, and verify the `importExecutor` thread pool (`AsyncConfig`) isn't exhausted.
2. **"Failed" with an error message** — the **Error** column shows the exception message. Most common: a bad `visit_date` — `Instant.parse` throws and fails the **whole** file (the parse runs in one `@Transactional` method, so one bad date = zero visits saved).
3. **Upload rejected before it starts** — the POST returns `400` for: empty file (`"File is empty"`) or a content-type that contains neither `csv` nor `text` (`"Only CSV files are accepted"`). The Svelte view also blocks non-`.csv` by filename client-side.
4. **Import "Completed" but Records looks wrong / too low** — `recordCount` counts **visits** (unique `atm_id|visit_date|vendor|reference` groups), not CSV rows. Multiple cassette/removal rows collapse into one visit. Also: rows shorter than the required column count are silently **skipped** (cassette needs 11 cols, removal needs 8, anything `< 7` cols dropped).
5. **Data imported but numbers are zero / types wrong** — malformed values are silently coerced, not failed: bad integer/long → `0`; unknown `cassette_type` → `DISPENSER`; unknown removal type → `REJECTED_NOTE`. A "Completed" import can still contain garbage counts. Verify the source CSV, not the app.
6. **Nothing shows in history for an MSP admin** — a multi-institution admin whose JWT has a null `institutionKey` will write imports/visits tagged `institutionKey = null` (the CSV path uses the principal key directly and does **not** derive institution from the ATM the way manual visit-create does). They then won't line up under any real institution. Upload as an institution-scoped user.

## Common failure modes → fix

| Symptom | Likely cause | Fix |
|---------|-------------|-----|
| `400 File is empty` | Zero-byte upload | Re-export the CSV |
| `400 Only CSV files are accepted` | Content-type not `*csv*`/`*text*` | Send with `text/csv`; note null content-type is **not** rejected by the API |
| Status `FAILED`, error mentions parse/`Instant` | Bad `visit_date` (not ISO-8601) | Fix date column; `Z`/UTC is appended if missing |
| Status `FAILED`, "CSV file is empty" | Header-only or truly empty body | File must have a header row **plus** data rows |
| Completed but half the rows missing | Short rows silently skipped | Ensure cassette rows have 11 cols, removal rows 8 |
| Counts all `0` / wrong cassette type | Silent coercion of bad values | Correct numeric/enum columns at source |
| Duplicate visits after re-upload | No dedup/idempotency on re-import | Don't re-upload the same file; delete dup visits via `DELETE /api/vault/visits/{id}` |

## Manual poke (with a real JWT)

```bash
# List import history for the caller's institution
curl -s -H "Authorization: Bearer $JWT" \
  "https://api.bcos.cloud/vault/imports?page=0&size=25"

# Single import status (what the UI polls)
curl -s -H "Authorization: Bearer $JWT" \
  "https://api.bcos.cloud/vault/imports/<uuid>"

# Upload a CSV (returns 202 + PROCESSING record, then poll the id above)
curl -s -H "Authorization: Bearer $JWT" \
  -F "file=@cit.csv;type=text/csv" \
  "https://api.bcos.cloud/vault/imports"
```

Uploads land in `UPLOADS_DIR` (default `/tmp/vault-uploads`); multipart max is 20MB. See [technical.vault] for the full service picture.
