---
name: jellyfin
description: Jellyfin media server management — API for libraries, items, streaming, users. Use when managing media library or checking Jellyfin status.
---

# Jellyfin Management

Container: `jellyfin` | Port: `8096` | IP: `172.16.238.20` (may change on restart)
API Token: `978033be716d46678a5d3c54ae0e0ff9`
Web UI: `https://jellyfin.tblindustries.be` (NO Authelia — native login, password: `coup1802`)
Config: `/home/timothy/downloadswarm/jellyfin/` on jazz

## Access Pattern

```bash
docker exec jellyfin curl -s 'http://localhost:8096/ENDPOINT' \
  -H 'X-Emby-Token: 978033be716d46678a5d3c54ae0e0ff9'
```

## Volume Mounts

| Host Path | Container Path | Content |
|-----------|---------------|---------|
| `/mnt/teramind/episodes` | `/data/tvshows` | TV shows |
| `/mnt/episodes` | `/data/episodes` | More episodes |
| `/mnt/media/movies` | `/data/movies` | Movies |
| `/mnt/media/standup` | `/data/standup` | Standup |
| `/mnt/media/music_videos` | `/data/music` | Music videos |
| `/mnt/media/audio/music` | `/data/audio` | Music audio (ro) |

## API Endpoints

### System
```
GET  /System/Info                   # Server info, version
GET  /System/Info/Public            # Public info (no auth needed)
POST /System/Restart                # Restart server
```

### Items (Search & Browse)
```bash
# Search items
GET /Items?includeItemTypes=Movie,Episode,Series&recursive=true&searchTerm=QUERY&fields=Path&limit=20

# Get item details
GET /Items?ids=ITEM_ID&fields=Path,MediaStreams,Overview

# Get all movies
GET /Items?includeItemTypes=Movie&recursive=true&fields=Path&limit=1000

# Get series
GET /Items?includeItemTypes=Series&recursive=true&fields=Path

# Get episodes for a series
GET /Shows/{seriesId}/Episodes?fields=Path,MediaStreams

# Filter by library (parentId)
GET /Items?parentId=LIBRARY_ID&recursive=true&fields=Path
```

### Libraries
```
GET  /Library/VirtualFolders        # List all libraries
POST /Library/Refresh               # Trigger full library scan
POST /Items/{id}/Refresh            # Refresh single item metadata
```

### Streaming
```bash
# Test stream URL
GET /Videos/{itemId}/stream?static=true

# Get playback info
GET /Items/{itemId}/PlaybackInfo
```

### Users
```
GET  /Users                         # List users
GET  /Users/{userId}                # User details
```

## Library IDs

Check with: `curl -s -H "X-Emby-Token: TOKEN" http://localhost:8096/Library/VirtualFolders`

## Live TV

- **ErsatzTV** (channels <1000): M3U `http://ersatztv:8409/iptv/channels.m3u`, XMLTV `http://ersatztv:8409/iptv/xmltv.xml`
- **Dispatcharr** (channels 1000+): IPTV stream manager on port 9191, separate tuner
- Configured in Jellyfin Admin > Live TV
- Guide refresh task ID: `bea9b218c97bbf98c5dc1303bdb9a0ca` — trigger via `POST /ScheduledTasks/Running/{id}`
- **Logo fix after guide refresh**: ErsatzTV logos break (aspect ratio=0) because M3U uses `localhost:8409`. Fix script in `docs/Docker/ErsatzTV.md` downloads from ETV and base64-uploads to `POST /Items/{id}/Images/Primary` (body = base64, Content-Type = image/png)
- **Image upload format**: Jellyfin expects base64-encoded body (NOT raw binary) for `POST /Items/{id}/Images/Primary`

## Gotchas

- **Passwords**: `coup1802` (NOT `ded89Lm4`) — Jellyfin has native auth, no Authelia
- Auth header is `X-Emby-Token` (Jellyfin is an Emby fork)
- **Music videos are typed `MusicVideo`, NOT `Movie`** (corrected 2026-07-21, ersatztv#177). The old
  "typed as Movie" note described a deliberate DB reclassification workaround that existed only because
  ErsatzTV could not consume `MusicVideo` items — ersatztv#42 shipped that sync, so the workaround's
  premise is gone. Verified live: the `Music Videos` library (`/data/music`, collection type
  `musicvideos`) holds 1437 items typed `MusicVideo` and **zero** typed `Movie`. Query with
  `includeItemTypes=MusicVideo`. (Reclassification to `Movie` may still apply to concert/standup content
  in the `movies`/`mixed` libraries — that is a different set; see the server-management jellyfin skill.)
- **`Album` is not an `ItemFields` value.** It is a plain `BaseItemDto` property serialized whenever set,
  so it comes back regardless of the `fields=` query param — do NOT add it to `fields` (verified: 111 of
  1437 music videos returned `Album` with `fields=Path` alone). Contrast `Genres`/`People`/`Chapters`,
  which ARE `ItemFields` and must be requested. Check the enum before extending `fields`.
- **`IndexNumber` is the track number; `ParentIndexNumber` is the disc/season axis.** Frequency misleads
  here — on the live music video library `ParentIndexNumber` is populated on 66 items vs 4 for
  `IndexNumber`, but where both exist `ParentIndexNumber` is `1` while `IndexNumber` holds the real
  ordinal, and where only `ParentIndexNumber` exists it is a collection grouping tracking the album
  (`Glastonbury: 2022` -> 230). `AlbumId` is always null on these items.
- Music library at `/data/music` maps to `/mnt/media/music_videos` on host (not actual music)
- Items return 404 on stream if source volume is unmounted
- Jellyfin preserves item IDs across restarts unless files are renamed
- Full library scan can take a long time — prefer targeted `/Items/{id}/Refresh`
- `ffprobe` available in container for checking media streams: `docker exec jellyfin ffprobe -v quiet -print_format json -show_streams FILE`
