plex-media-server
Scannednpx machina-cli add skill ddnetters/homelab-agent-skills/plex-media-server --openclawPlex Media Server
Manage your Plex Media Server: scan libraries, search media, monitor playback sessions, and automate maintenance.
Authentication
All Plex API calls require an X-Plex-Token. Find yours:
- Open Plex Web → any media item → Get Info → View XML → check URL for
X-Plex-Token= - Or from the Plex claim:
curl -s "https://plex.tv/api/v2/user" -H "X-Plex-Token: YOUR_TOKEN" | jq .
export PLEX_URL="http://localhost:32400"
export PLEX_TOKEN="YOUR_PLEX_TOKEN"
Server status
# Server identity
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
-H "Accept: application/json" \
"$PLEX_URL/identity" | jq .
# Server capabilities
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
-H "Accept: application/json" \
"$PLEX_URL/" | jq '.MediaContainer | {friendlyName, version, platform, updatedAt}'
# Server preferences
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
-H "Accept: application/json" \
"$PLEX_URL/:/prefs" | jq '.MediaContainer.Setting[] | {id, value}'
Libraries
List libraries
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
-H "Accept: application/json" \
"$PLEX_URL/library/sections" | \
jq '.MediaContainer.Directory[] | {key, title, type, agent, scanner}'
Scan library
# Scan specific library (use key from list)
curl -s -X POST -H "X-Plex-Token: $PLEX_TOKEN" \
"$PLEX_URL/library/sections/SECTION_KEY/refresh"
# Scan all libraries
curl -s -X POST -H "X-Plex-Token: $PLEX_TOKEN" \
"$PLEX_URL/library/sections/all/refresh"
# Force full metadata refresh
curl -s -X POST -H "X-Plex-Token: $PLEX_TOKEN" \
"$PLEX_URL/library/sections/SECTION_KEY/refresh?force=1"
Browse library contents
# List all items in a library section
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
-H "Accept: application/json" \
"$PLEX_URL/library/sections/SECTION_KEY/all" | \
jq '.MediaContainer.Metadata[] | {title, year, rating, addedAt}'
# Recently added
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
-H "Accept: application/json" \
"$PLEX_URL/library/recentlyAdded" | \
jq '.MediaContainer.Metadata[] | {title, type, year, addedAt}'
# On deck (continue watching)
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
-H "Accept: application/json" \
"$PLEX_URL/library/onDeck" | \
jq '.MediaContainer.Metadata[] | {title, grandparentTitle, viewOffset, duration}'
Search
# Search across all libraries
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
-H "Accept: application/json" \
"$PLEX_URL/hubs/search?query=inception" | \
jq '.MediaContainer.Hub[] | {type, size, Metadata: [.Metadata[]? | {title, year, type}]}'
# Search specific library
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
-H "Accept: application/json" \
"$PLEX_URL/library/sections/SECTION_KEY/search?query=inception" | \
jq '.MediaContainer.Metadata[] | {title, year, summary}'
Playback sessions
Currently playing
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
-H "Accept: application/json" \
"$PLEX_URL/status/sessions" | \
jq '.MediaContainer.Metadata[]? | {
title,
user: .User.title,
player: .Player.title,
state: .Player.state,
progress: ((.viewOffset / .duration * 100) | round),
transcoding: (.TranscodeSession != null)
}'
Playback history
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
-H "Accept: application/json" \
"$PLEX_URL/status/sessions/history/all" | \
jq '.MediaContainer.Metadata[] | {title, viewedAt, accountID}'
Media info
Get item details
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
-H "Accept: application/json" \
"$PLEX_URL/library/metadata/RATING_KEY" | \
jq '.MediaContainer.Metadata[0] | {title, year, summary, rating, Media: [.Media[] | {videoResolution, bitrate, container}]}'
Get TV show seasons and episodes
# Seasons of a show
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
-H "Accept: application/json" \
"$PLEX_URL/library/metadata/SHOW_RATING_KEY/children" | \
jq '.MediaContainer.Metadata[] | {title, index, leafCount}'
# Episodes of a season
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
-H "Accept: application/json" \
"$PLEX_URL/library/metadata/SEASON_RATING_KEY/children" | \
jq '.MediaContainer.Metadata[] | {title, index, duration}'
Maintenance
Optimize database
curl -s -X PUT -H "X-Plex-Token: $PLEX_TOKEN" \
"$PLEX_URL/library/optimize"
Clean bundles (remove orphaned data)
curl -s -X PUT -H "X-Plex-Token: $PLEX_TOKEN" \
"$PLEX_URL/library/clean/bundles"
Empty trash for a library
curl -s -X PUT -H "X-Plex-Token: $PLEX_TOKEN" \
"$PLEX_URL/library/sections/SECTION_KEY/emptyTrash"
Analyze media (detect intros, credits)
# Analyze all items in a library
curl -s -X POST -H "X-Plex-Token: $PLEX_TOKEN" \
"$PLEX_URL/library/sections/SECTION_KEY/analyze"
Playback control
# Get list of available players
curl -s -H "X-Plex-Token: $PLEX_TOKEN" \
-H "Accept: application/json" \
"$PLEX_URL/clients" | \
jq '.MediaContainer.Server[] | {name, address, port, machineIdentifier}'
Docker deployment
services:
plex:
image: lscr.io/linuxserver/plex:latest
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Amsterdam
- VERSION=docker
- PLEX_CLAIM=claim-XXXXX # From https://plex.tv/claim
ports:
- "32400:32400"
volumes:
- ./plex-config:/config
- /path/to/movies:/movies
- /path/to/tv:/tv
restart: unless-stopped
Webhooks
Plex can send webhooks on playback events (requires Plex Pass):
Events: media.play, media.pause, media.resume, media.stop, media.scrobble, library.new, library.on.deck
Configure in Settings → Webhooks → Add Webhook URL.
Example: ntfy notification on new media
# Receive webhook and forward to ntfy (simple Flask/Node server)
# Plex sends POST with JSON payload containing event type and metadata
# Parse and forward:
curl -H "Authorization: Bearer $NTFY_TOKEN" \
-H "Tags: movie_camera" \
-d "New on Plex: $TITLE ($YEAR)" \
"https://ntfy.example.com/plex"
Integration with Radarr/Sonarr
Trigger Plex library scan after Radarr/Sonarr imports:
- Radarr: Settings → Connect → Add → Plex Media Server → enter host, port, token
- Sonarr: Settings → Connect → Add → Plex Media Server → same config
This automatically scans the relevant library section after new media is imported.
Troubleshooting
| Problem | Fix |
|---|---|
| Library not showing new files | Trigger manual scan: POST /library/sections/KEY/refresh |
| Remote access not working | Check port 32400 forwarded, or use reverse proxy. Check Settings → Remote Access |
| Transcoding issues | Check hardware transcoding enabled. Verify /dev/dri mapped for Intel QSV |
| Slow library scan | Run optimize and clean/bundles. Check disk I/O |
| Metadata wrong | Force refresh: PUT /library/metadata/KEY/refresh?force=1 |
| "Not authorized" | Token expired or wrong. Re-fetch from Plex Web UI |
| Database locked | Stop Plex, check for com.plexapp.plugins.library.db-wal, restart |
Source
git clone https://github.com/ddnetters/homelab-agent-skills/blob/main/plex-media-server/SKILL.mdView on GitHub Overview
This skill exposes Plex Media Server’s REST API to manage libraries, perform media searches, monitor playback sessions, and automate maintenance. It uses an X-Plex-Token for authentication and supports tasks like scanning libraries, querying server status, and retrieving media details.
How This Skill Works
Authenticate requests with the X-Plex-Token header (set PLEX_TOKEN). Use PLEX_URL as the base for endpoints such as /library/sections, /status/sessions, /library/sections/SECTION_KEY/refresh, and /library/metadata/RATING_KEY to interact with libraries, playback, and media info. Responses are JSON and commonly parsed with tools like jq, following examples in the skill markdown for identity, server status, and media data.
When to Use It
- Refresh a single library or all libraries to update metadata and file status.
- Browse a library’s contents or use Recently Added / On Deck views to surface items.
- Search across all libraries or within a specific library for titles, years, or summaries.
- Monitor who is playing media and the current state with status/sessions.
- Check server identity, capabilities, and preferences to inform automation.
Quick Start
- Step 1: Set PLEX_URL and PLEX_TOKEN (export PLEX_URL=http://localhost:32400; export PLEX_TOKEN=YOUR_TOKEN).
- Step 2: List libraries to discover SECTION_KEY values (curl -s -H \"X-Plex-Token: $PLEX_TOKEN\" -H \"Accept: application/json\" \"$PLEX_URL/library/sections\" | jq '.MediaContainer.Directory[] | {key, title, type}').
- Step 3: Try a sample action like scanning a library or performing a search (e.g., curl -s -X POST -H \"X-Plex-Token: $PLEX_TOKEN\" \"$PLEX_URL/library/sections/SECTION_KEY/refresh\").
Best Practices
- Securely store and reuse X-Plex-Token; do not hardcode it.
- Discover library sections first (library/sections) before targeting specific keys.
- Call specific endpoints (e.g., /library/sections/SECTION_KEY/refresh) to avoid heavy scans.
- Use Accept: application/json and parse results with jq to extract fields.
- Handle errors and edge cases (missing tokens, 404s, rate limits) gracefully.
Example Use Cases
- curl -s -H \"X-Plex-Token: $PLEX_TOKEN\" -H \"Accept: application/json\" \"$PLEX_URL/library/sections\" | jq '.MediaContainer.Directory[] | {key, title, type, agent, scanner}'
- curl -s -X POST -H \"X-Plex-Token: $PLEX_TOKEN\" \"$PLEX_URL/library/sections/SECTION_KEY/refresh\"
- curl -s -H \"X-Plex-Token: $PLEX_TOKEN\" -H \"Accept: application/json\" \"$PLEX_URL/library/sections/SECTION_KEY/all\" | jq '.MediaContainer.Metadata[] | {title, year, rating, addedAt}'
- curl -s -H \"X-Plex-Token: $PLEX_TOKEN\" -H \"Accept: application/json\" \"$PLEX_URL/hubs/search?query=inception\" | jq '.MediaContainer.Hub[] | {type, size, Metadata: [.Metadata[]? | {title, year, type}]}''
- curl -s -H \"X-Plex-Token: $PLEX_TOKEN\" -H \"Accept: application/json\" \"$PLEX_URL/status/sessions\" | jq '.MediaContainer.Metadata[]? | {title, user: .User.title, player: .Player.title, state: .Player.state, progress: ((.viewOffset / .duration * 100) | round), transcoding: (.TranscodeSession != null)}'