# Immich API: Job Management & GPU Troubleshooting

> Session-specific commands for triggering ML jobs and diagnosing GPU acceleration on the Immich machine-learning container (v2.7.5+).

## Authentication

### Login (get bearer token)

```bash
LOGIN=$(curl -s -X POST http://localhost:2283/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@example.com","password":"your-password"}')
TOK=*** -e "console.log(JSON.parse(process.argv[1]).accessToken)" -- "$LOGIN")
```

The token is a bearer token, used as `Authorization: Bearer *** all subsequent API calls.

## Job Management

### List all job queues

```bash
curl -s http://localhost:2283/api/jobs -H "Authorization: Bearer ***
```

Returns each job name with `queueStatus` (isPaused, isActive) and `jobCounts` (active, completed, failed, delayed, waiting, paused).

### Trigger a specific job

```bash
curl -s -X PUT "http://localhost:2283/api/jobs/smartSearch" \
  -H "Authorization: Bearer *** \
  -H "Content-Type: application/json" \
  -d '{"command":"start"}'
```

To force re-process already-completed assets, use `{"command":"start","force":true}`.

### Available job names

- `smartSearch` — CLIP embeddings for natural language search
- `faceDetection` — detect faces in photos
- `facialRecognition` — group detected faces into people
- `metadataExtraction` — read EXIF dates, GPS, camera info
- `thumbnailGeneration` — create preview thumbnails
- `videoConversion` — transcode videos for streaming
- `ocr` — read text in photos
- `duplicateDetection` — find duplicate assets
- `sidecar` — process sidecar files (XMP, etc.)
- `library` — scan library for new/changed files
- `backupDatabase` — create DB backup
- `notifications` — process notification queue
- `storageTemplateMigration` — move files to organized folder structure

## GPU Verification

### Check ONNX Runtime CUDA availability inside container

```bash
docker exec immich_machine_learning bash -c \
  'source /opt/venv/bin/activate && \
   python -c "import onnxruntime; print(onnxruntime.get_device()); print(onnxruntime.get_available_providers())"'
```

Expected output for GPU-enabled:
```
GPU
['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider']
```

### Check GPU device files in container

```bash
docker exec immich_machine_learning ls -la /dev | grep nvidia
```

Should show: `nvidia0`, `nvidiactl`, `nvidia-uvm`, `nvidia-uvm-tools`

### Verify compose GPU config is attached

```bash
docker inspect immich_machine_learning --format '{{json .HostConfig.DeviceRequests}}'
```

Should show: `[{"Driver":"nvidia","Count":-1,...}]` for `count: all`

Check runtime: `docker inspect immich_machine_learning --format '{{json .HostConfig.Runtime}}'`

### Immich server container has Node.js, not Python

For inline JSON parsing inside `docker exec immich_server` commands, use Node:

```bash
# Instead of python3 -c (not available), use:
docker exec immich_server node -e "console.log(JSON.parse(process.argv[1]).accessToken)" -- "$JSON_DATA"
```

## Docker Networking Recovery

If the Immich server cannot reach its database or redis (EHOSTUNREACH errors):
```
Error: connect EHOSTUNREACH 172.18.0.3:5432
Error: connect EHOSTUNREACH 172.18.0.4:6379
```

**Fix:** Restart the stack in dependency order:
```bash
cd /opt/immich && docker compose restart database redis
# Wait 5s for DB/redis to be ready
docker compose restart immich-server immich-machine-learning
```

## Docker Compose GPU Configuration (v2 format)

**Required** for the `v2-cuda` ML image to actually access the GPU:

```yaml
  immich-machine-learning:
    container_name: immich_machine_learning
    image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}-cuda
    volumes:
      - model-cache:/cache
    env_file:
      - .env
    restart: always
    healthcheck:
      disable: false
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]
```

The `v2-cuda` image already has `DEVICE=cuda` and `NVIDIA_VISIBLE_DEVICES=all` in its Dockerfile ENV — no need to add those in compose. The missing piece is the `deploy.resources.reservations.devices` block.

**⚠️ Container has no `curl`:** The ML container has no `curl` or `ping` installed. For network tests, use Python's `urllib.request` from inside the activated venv:
```bash
docker exec immich_machine_learning bash -c 'source /opt/venv/bin/activate && python -c "
import urllib.request
r = urllib.request.urlopen('\''https://huggingface.co'\'', timeout=10)
print(r.status)
"'
```

## GPU Functional Verification

### Layered diagnostic procedure

When the ML container shows models failing to load, use this progression to isolate the issue:

**Layer 1 — GPU hardware is exposed to container:**
```bash
docker exec immich_machine_learning ls -la /dev | grep nvidia
# Must show: nvidia0, nvidiactl, nvidia-uvm
```

**Layer 2 — ONNX Runtime detects CUDA providers:**
```bash
docker exec immich_machine_learning bash -c 'source /opt/venv/bin/activate && python -c "
import onnxruntime
print(\"Device:\", onnxruntime.get_device())
print(\"Providers:\", onnxruntime.get_available_providers())
"'
# Expected: Device=GPU, Providers=[TensorrtExecutionProvider, CUDAExecutionProvider, CPUExecutionProvider]
```

**Layer 3 — Actual CUDA inference works:**
```bash
docker exec immich_machine_learning bash -c 'source /opt/venv/bin/activate && python -c "
import onnxruntime as ort, numpy as np, onnx
from onnx import helper, TensorProto

# Build a minimal model (Relu) and run it on CUDA
X = helper.make_tensor_value_info(\"X\", TensorProto.FLOAT, [1, 3])
Y = helper.make_tensor_value_info(\"Y\", TensorProto.FLOAT, [1, 3])
node = helper.make_node(\"Relu\", [\"X\"], [\"Y\"])
graph = helper.make_graph([node], \"test\", [X], [Y])
model = helper.make_model(graph)

sess = ort.InferenceSession(model.SerializeToString(),
  providers=[\"CUDAExecutionProvider\", \"CPUExecutionProvider\"])
result = sess.run(None, {\"X\": np.array([[-2.0, 0.0, 2.0]], dtype=np.float32)})
print(\"CUDA inference OK:\", result[0])
"'
# Expected: CUDA inference OK: [[0. 0. 2.]]
```

**Layer 4 — Load a real CLIP model on GPU (after download):**
```bash
# Download the CLIP model first (run this step if not yet cached)
docker exec immich_machine_learning bash -c 'source /opt/venv/bin/activate && python -c "
from huggingface_hub import snapshot_download, os
cache_dir = \"/cache/clip/ViT-B-32__openai\"
os.makedirs(cache_dir, exist_ok=True)
snapshot_download(\"immich-app/ViT-B-32__openai\", cache_dir=cache_dir, local_dir=cache_dir,
  ignore_patterns=[\"*.armnn\", \"*.rknn\"], max_workers=2)
"'

# Then load and run inference on GPU
docker exec immich_machine_learning bash -c 'source /opt/venv/bin/activate && python -c "
import onnxruntime as ort, numpy as np, time, os
model_path = \"/cache/clip/ViT-B-32__openai/visual/model.onnx\"
print(f\"Model: {os.path.getsize(model_path)/1024/1024:.0f} MB\")

so = ort.SessionOptions()
so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
sess = ort.InferenceSession(model_path, sess_options=so,
  providers=[\"CUDAExecutionProvider\", \"CPUExecutionProvider\"],
  provider_options=[{\"device_id\": \"0\", \"gpu_mem_limit\": 3*1024*1024*1024}, {}])
print(f\"Loaded in {time.time():.0f}s\")

# Run one inference
data = np.random.randn(1, 3, 224, 224).astype(np.float32)
start = time.time()
outputs = sess.run(None, {sess.get_inputs()[0].name: data})
print(f\"Inference: {outputs[0].shape} in {(time.time()-start)*1000:.0f}ms\")
"'
# Expected: ~0.8s load, ~237ms inference, output shape (1, 512)
```

### Common failures at each layer

| Layer | Failure Symptom | Likely Cause |
|-------|----------------|--------------|
| 1 | No nvidia device files | GPU not configured in compose — add `deploy.resources.reservations.devices` |
| 1 | nvidia0 present but nvidia-smi not found | Container image has drivers but no nvidia-smi binary (normal for Immich ML image) |
| 2 | Only CPUExecutionProvider | GPU not exposed to container (wrong runtime / no device requests) |
| 3 | ONNX session creation fails | CUDA version mismatch (container CUDA 12.2 vs host driver) or OOM |
| 4 | Model download OK but load fails | Disk space, corrupt download, or model file mismatch. Check /cache mounts |
| 4 | Download loop (clear→retry→fail) | Multiple models writing to same cache_dir, overwriting each other's files. Try MACHINE_LEARNING_MODEL_ARENA=true
