---
module: fleet.artifacts
title: Artifact Import Guide
tab: Claude
order: 40
audience: dev
---

> Act-without-guessing cheat-sheet. Owner service: **hiveops-fleet** (`com.hiveops.fleet`, port 8097, DB `hiveiq_fleet`). Never write artifact data anywhere else.

## Ownership
- Table **`fleet_artifacts`** (DB `hiveiq_fleet`) is the ONLY store for artifacts. Column `cdn_url` added in `V7__add_artifact_cdn_url.sql`.
- Component: `frontend/src/components/FleetManagement/FleetArtifacts.svelte`.
- Service: `FleetArtifactService`. User REST: `FleetApiController`. Agent REST: `InternalFleetController`.

## Endpoints (METHOD path → roles)
| METHOD path | Roles |
|-------------|-------|
| GET `/api/fleet/artifacts` (opt `?type=<ArtifactType>`) | USER,CUSTOMER,ADMIN,MSP_ADMIN,BCOS_ADMIN |
| GET `/api/fleet/artifacts/{id}` | same read roles |
| POST `/api/fleet/artifacts` (multipart) | ADMIN,MSP_ADMIN,BCOS_ADMIN |
| GET `/api/fleet/cdn-manifest/{type}` | MSP_ADMIN,BCOS_ADMIN |
| POST `/api/fleet/artifacts/import-cdn` (JSON) | ADMIN,MSP_ADMIN,BCOS_ADMIN |
| POST `/api/fleet/artifacts/{id}/toggle-enabled` | ADMIN,MSP_ADMIN,BCOS_ADMIN |
| DELETE `/api/fleet/artifacts/{id}` | ADMIN,MSP_ADMIN,BCOS_ADMIN |
| GET `/api/internal/fleet/tasks/{taskId}/download?offset=N` | ROLE_INTERNAL (`X-Internal-Secret`) |

Base URL: `https://api.bcos.cloud/fleet` (prod) / `https://api.bcos.dev/fleet` (dev). Frontend `fleetAPI` prefixes `/api/fleet`.

## Request shapes
- **multipart** `POST /artifacts` params: `file`, `name`, `version`, `artifactType`, `description?`, `institutionKey?`.
- **import-cdn** `POST /artifacts/import-cdn` JSON body: `{ "cdnUrl", "name", "version", "artifactType", "description"?, "institutionKey"? }`.

## `ArtifactType` (exact enum values)
`UPDATE_AGENT` · `UPDATE_HOTFIX` · `UPDATE_SOFTWARE` · `UPDATE_MODULE` · `SCRIPT`
(UI labels: Agent Patch / Hotfix / Software Update / Module Update / Script — labels are NOT the stored values.)

## CDN manifest types (path `{type}`)
Only `agent`, `hotfix`, `software` are valid server-side → resolve to `https://cdn.bcos.cloud/downloads/{type}/latest.json`. Any other `{type}` (e.g. `modules`) ⇒ `400 Unknown manifest type`.

## Topics
- Artifact import emits **NO Kafka event** (synchronous DB write only).
- Fleet's task topic is `hiveops.fleet.task.events` (tasks, not artifacts).
- `device_summary` is synced from `hiveops.device.events` (read-only mirror; do not write it here).

## Env / config
- `CDN_TOKEN` → `cdn.token` → sent as `X-CDN-Token`.
- `FLEET_UPLOAD_DIR` → `fleet.artifact.upload.dir` (default `/app/uploads/fleet`).
- Multipart cap `100MB`. `INTERNAL_SECRET`, `JWT_SECRET`, `DB_URL` standard.

## Gotchas
- CDN import does **HEAD only** — file is NOT stored in fleet. Row has `cdn_url`, no `sha256_hash`, no `storage_path`. Agent downloads from CDN directly (`artifactCdnUrl` in `tasks/next`).
- Uploads (multipart) DO store `storage_path` + `sha256_hash`; agent downloads chunked via internal endpoint.
- `readChunk` on a CDN-only artifact throws `410 GONE` — CDN artifacts must be pulled from `cdnUrl`, never fleet's chunk endpoint.
- Delete blocked (`409 CONFLICT`) while any `PENDING/QUEUED/RUNNING` task references the artifact.
- `institution_key` null ⇒ Global (all institutions); set ⇒ scoped; max length 10.
- `enabled` defaults true; toggled via `/toggle-enabled`.
- CDN download bypasses fleet, so task `download_offset` staying 0 is NORMAL, not a stall.

## Do NOT
- ❌ Write artifact rows to any DB other than `hiveiq_fleet.fleet_artifacts`.
- ❌ Add an artifact endpoint on incident / devices / mgmt — fleet owns artifacts.
- ❌ Use UI labels ("Agent Patch") as `artifactType` — send the enum value (`UPDATE_AGENT`).
- ❌ Call `/cdn-manifest/modules` — unsupported, 400.
- ❌ Expect a SHA-256 on CDN-imported artifacts — there isn't one.
- ❌ Point an agent at `/tasks/{id}/download` for a CDN artifact — expect 410.
- ❌ Fetch manifests without `X-CDN-Token` in a token-gated environment — expect 401/403.
