arr-media-stack
npx machina-cli add skill ddnetters/homelab-agent-skills/arr-media-stack --openclawArr Media Stack
Manage your automated media stack: Radarr (movies), Sonarr (TV), Prowlarr (indexers), Bazarr (subtitles), and qBittorrent (downloads).
All APIs use X-Api-Key header for auth. Base URLs default to http://localhost:<port>/api/v3 (Radarr/Sonarr/Prowlarr) or http://localhost:<port>/api/v2 (Bazarr).
Radarr (Movies) — port 7878
Search and add a movie
# Search by name
curl -s -H "X-Api-Key: YOUR_RADARR_KEY" \
"http://localhost:7878/api/v3/movie/lookup?term=inception" | jq '.[0] | {title, year, tmdbId}'
# Add movie (use tmdbId from lookup)
curl -s -X POST -H "X-Api-Key: YOUR_RADARR_KEY" \
-H "Content-Type: application/json" \
-d '{
"tmdbId": 27205,
"title": "Inception",
"qualityProfileId": 1,
"rootFolderPath": "/movies",
"monitored": true,
"addOptions": {"searchForMovie": true}
}' "http://localhost:7878/api/v3/movie"
List and manage movies
# List all movies
curl -s -H "X-Api-Key: YOUR_RADARR_KEY" \
"http://localhost:7878/api/v3/movie" | jq '.[] | {id, title, year, monitored, hasFile}'
# Get specific movie
curl -s -H "X-Api-Key: YOUR_RADARR_KEY" \
"http://localhost:7878/api/v3/movie/1"
# Delete movie (with files)
curl -s -X DELETE -H "X-Api-Key: YOUR_RADARR_KEY" \
"http://localhost:7878/api/v3/movie/1?deleteFiles=true"
# Trigger search for missing movies
curl -s -X POST -H "X-Api-Key: YOUR_RADARR_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "MissingMoviesSearch"}' \
"http://localhost:7878/api/v3/command"
Queue and downloads
# View download queue
curl -s -H "X-Api-Key: YOUR_RADARR_KEY" \
"http://localhost:7878/api/v3/queue" | jq '.records[] | {title, status, sizeleft, timeleft}'
# System health
curl -s -H "X-Api-Key: YOUR_RADARR_KEY" \
"http://localhost:7878/api/v3/health" | jq '.[] | {type, message}'
# Root folders
curl -s -H "X-Api-Key: YOUR_RADARR_KEY" \
"http://localhost:7878/api/v3/rootfolder" | jq '.[] | {path, freeSpace}'
# Quality profiles
curl -s -H "X-Api-Key: YOUR_RADARR_KEY" \
"http://localhost:7878/api/v3/qualityprofile" | jq '.[] | {id, name}'
Sonarr (TV Shows) — port 8989
Search and add a series
# Search by name
curl -s -H "X-Api-Key: YOUR_SONARR_KEY" \
"http://localhost:8989/api/v3/series/lookup?term=breaking+bad" | jq '.[0] | {title, year, tvdbId}'
# Add series
curl -s -X POST -H "X-Api-Key: YOUR_SONARR_KEY" \
-H "Content-Type: application/json" \
-d '{
"tvdbId": 81189,
"title": "Breaking Bad",
"qualityProfileId": 1,
"rootFolderPath": "/tv",
"monitored": true,
"addOptions": {"searchForMissingEpisodes": true}
}' "http://localhost:8989/api/v3/series"
List and manage series
# List all series
curl -s -H "X-Api-Key: YOUR_SONARR_KEY" \
"http://localhost:8989/api/v3/series" | jq '.[] | {id, title, year, monitored, episodeFileCount, episodeCount}'
# Upcoming episodes
curl -s -H "X-Api-Key: YOUR_SONARR_KEY" \
"http://localhost:8989/api/v3/calendar?start=$(date +%F)&end=$(date -d '+7 days' +%F)" | \
jq '.[] | {series: .series.title, episode: .title, airDate: .airDate}'
# Missing episodes
curl -s -H "X-Api-Key: YOUR_SONARR_KEY" \
"http://localhost:8989/api/v3/wanted/missing?pageSize=20" | jq '.records[] | {series: .series.title, episode: .title, airDate: .airDate}'
# Trigger search for missing episodes
curl -s -X POST -H "X-Api-Key: YOUR_SONARR_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "MissingEpisodeSearch"}' \
"http://localhost:8989/api/v3/command"
# Queue
curl -s -H "X-Api-Key: YOUR_SONARR_KEY" \
"http://localhost:8989/api/v3/queue" | jq '.records[] | {title, status, sizeleft, timeleft}'
Prowlarr (Indexers) — port 9696
Manage indexers
# List indexers
curl -s -H "X-Api-Key: YOUR_PROWLARR_KEY" \
"http://localhost:9696/api/v1/indexer" | jq '.[] | {id, name, enable, protocol}'
# Test indexer
curl -s -X POST -H "X-Api-Key: YOUR_PROWLARR_KEY" \
-H "Content-Type: application/json" \
-d '{"id": 1}' \
"http://localhost:9696/api/v1/indexer/test"
# Sync indexers to Radarr/Sonarr
curl -s -X POST -H "X-Api-Key: YOUR_PROWLARR_KEY" \
"http://localhost:9696/api/v1/application/action/sync"
# List linked applications
curl -s -H "X-Api-Key: YOUR_PROWLARR_KEY" \
"http://localhost:9696/api/v1/application" | jq '.[] | {id, name, syncLevel}'
# Search across all indexers
curl -s -H "X-Api-Key: YOUR_PROWLARR_KEY" \
"http://localhost:9696/api/v1/search?query=inception&type=movie" | \
jq '.[] | {title, indexer, size, seeders}'
FlareSolverr (Cloudflare bypass)
FlareSolverr runs alongside Prowlarr to bypass Cloudflare-protected indexers.
# Health check
curl -s http://localhost:8191/v1 -d '{"cmd": "sessions.list"}' -H "Content-Type: application/json"
Configure in Prowlarr: Settings → Indexer Proxies → Add → FlareSolverr → http://flaresolverr:8191
Bazarr (Subtitles) — port 6767
# List wanted subtitles (movies)
curl -s -H "X-Api-Key: YOUR_BAZARR_KEY" \
"http://localhost:6767/api/movies/wanted" | jq '.data[] | {title, missing_subtitles}'
# List wanted subtitles (series)
curl -s -H "X-Api-Key: YOUR_BAZARR_KEY" \
"http://localhost:6767/api/episodes/wanted" | jq '.data[] | {seriesTitle, episodeTitle, missing_subtitles}'
# Trigger search for wanted subtitles
curl -s -X POST -H "X-Api-Key: YOUR_BAZARR_KEY" \
"http://localhost:6767/api/subtitles/wanted/movies"
# System health
curl -s -H "X-Api-Key: YOUR_BAZARR_KEY" \
"http://localhost:6767/api/system/health"
qBittorrent — port 8080
qBittorrent uses cookie-based auth. Login first, then use the cookie.
# Login (save cookie)
curl -s -c /tmp/qbt-cookies.txt \
-d "username=admin&password=YOUR_PASSWORD" \
"http://localhost:8080/api/v2/auth/login"
# List all torrents
curl -s -b /tmp/qbt-cookies.txt \
"http://localhost:8080/api/v2/torrents/info" | \
jq '.[] | {name, state, progress, dlspeed, size}'
# List active downloads
curl -s -b /tmp/qbt-cookies.txt \
"http://localhost:8080/api/v2/torrents/info?filter=downloading" | \
jq '.[] | {name, progress: (.progress * 100 | round), dlspeed, eta}'
# Pause/resume torrent
curl -s -b /tmp/qbt-cookies.txt -d "hashes=TORRENT_HASH" \
"http://localhost:8080/api/v2/torrents/pause"
curl -s -b /tmp/qbt-cookies.txt -d "hashes=TORRENT_HASH" \
"http://localhost:8080/api/v2/torrents/resume"
# Delete torrent (with files)
curl -s -b /tmp/qbt-cookies.txt -d "hashes=TORRENT_HASH&deleteFiles=true" \
"http://localhost:8080/api/v2/torrents/delete"
# Transfer info (global speeds)
curl -s -b /tmp/qbt-cookies.txt \
"http://localhost:8080/api/v2/transfer/info" | jq '{dl_info_speed, up_info_speed}'
VPN container networking
qBittorrent commonly runs through a VPN container (nordvpn, gluetun, etc.):
services:
vpn:
image: bubuntux/nordlynx # or qmcgaw/gluetun
cap_add: [NET_ADMIN]
ports:
- "8080:8080" # qBittorrent WebUI exposed through VPN
environment:
- PRIVATE_KEY=your-wireguard-key
qbittorrent:
image: lscr.io/linuxserver/qbittorrent
network_mode: "service:vpn" # Route all traffic through VPN
depends_on: [vpn]
Overseerr (Media Requests) — port 5055
# Search
curl -s -H "X-Api-Key: YOUR_OVERSEERR_KEY" \
"http://localhost:5055/api/v1/search?query=inception" | jq '.results[] | {title, mediaType, year}'
# Request a movie
curl -s -X POST -H "X-Api-Key: YOUR_OVERSEERR_KEY" \
-H "Content-Type: application/json" \
-d '{"mediaType": "movie", "mediaId": 27205}' \
"http://localhost:5055/api/v1/request"
# List pending requests
curl -s -H "X-Api-Key: YOUR_OVERSEERR_KEY" \
"http://localhost:5055/api/v1/request?filter=pending" | jq '.results[] | {id, type: .type, status: .status, media: .media.tmdbId}'
Common storage layout
/opt/media/
├── downloads/ # qBittorrent download directory
├── movies/ # Radarr movie library
├── tv/ # Sonarr TV library
├── radarr/ # Radarr config
├── sonarr/ # Sonarr config
├── prowlarr/ # Prowlarr config
└── qbittorrent/ # qBittorrent config
Troubleshooting
| Problem | Service | Fix |
|---|---|---|
| Download stuck at 100% | Radarr/Sonarr | Check import errors in Activity → Queue. Usually permissions or path mapping |
| No results from search | Prowlarr | Check indexer health. Test individual indexers. Try FlareSolverr for Cloudflare sites |
| "Path does not exist" | Radarr/Sonarr | Docker volume mounts don't match. Ensure paths inside container match config |
| Torrent stalled | qBittorrent | Check VPN connection. Restart VPN container. Verify port forwarding |
| Subtitle not found | Bazarr | Check provider API limits. Add more subtitle providers in Settings |
| Can't reach qBittorrent UI | qBittorrent | It's behind VPN container. Check VPN container ports, not qBittorrent's |
| Import failed | Radarr/Sonarr | Check PUID/PGID match between containers. Verify write permissions on library folder |
Health check all services
for svc in "radarr:7878" "sonarr:8989" "prowlarr:9696"; do
name="${svc%%:*}"; port="${svc##*:}"
status=$(curl -s -o /dev/null -w "%{http_code}" -H "X-Api-Key: YOUR_KEY" \
"http://localhost:$port/api/v3/system/status")
echo "$name: $status"
done
Source
git clone https://github.com/ddnetters/homelab-agent-skills/blob/main/arr-media-stack/SKILL.mdView on GitHub Overview
Arr Media Stack orchestrates Radarr (movies), Sonarr (TV), Prowlarr (indexers), Bazarr (subtitles), and qBittorrent (downloads) via their APIs. All APIs require an X-Api-Key header, and base URLs default to http://localhost:<port>/api/v3 for Radarr/Sonarr/Prowlarr and http://localhost:<port>/api/v2 for Bazarr. This setup enables searching, adding, monitoring, and troubleshooting downloads in a unified workflow.
How This Skill Works
The skill uses RESTful API calls to each application’s endpoints (Radarr/Sonarr/Prowlarr on port 7878/8989/9696 with /api/v3, Bazarr on /api/v2). Typical actions include searching by name, adding items by ID (tmdbId/tvdbId), listing and managing items, viewing queues and health, and triggering automatic searches via command endpoints. This enables automated media management across the entire stack from a single interface.
When to Use It
- Add a new movie or TV show by name or ID and start an initial search.
- Trigger automated searches for missing episodes or movies to complete your library.
- Monitor download queues, system health, and disk space to prevent gaps in delivery.
- Review root folders and quality profiles to ensure correct storage and quality handling.
- Manage indexers via Prowlarr or coordinate subtitle workflows with Bazarr.
Quick Start
- Step 1: Gather API keys for Radarr, Sonarr, Prowlarr, Bazarr, and qBittorrent; note their base URLs and ports.
- Step 2: Try a simple search, e.g., Radarr movie lookup for a title you want to add.
- Step 3: Add the movie with tmdbId and enable searchForMovie to auto-fetch metadata and downloads.
Best Practices
- Use the lookup and add patterns shown (e.g., /movie/lookup and /series/lookup) to verify titles before adding.
- When adding, pass addOptions (e.g., searchForMovie: true or searchForMissingEpisodes: true) to accelerate retrieval.
- Keep API keys secure and rotate them periodically; avoid exposing keys in logs or scripts.
- Validate IDs (tmdbId, tvdbId) before add operations to prevent incorrect entries.
- Test connectivity with silent curl commands and parse responses with jq to confirm success.
Example Use Cases
- Search a movie by name: curl -s -H 'X-Api-Key: YOUR_RADARR_KEY' 'http://localhost:7878/api/v3/movie/lookup?term=inception' | jq '.[0] | {title, year, tmdbId}'
- Add a movie using tmdbId: curl -s -X POST -H 'X-Api-Key: YOUR_RADARR_KEY' -H 'Content-Type: application/json' -d '{"tmdbId": 27205, "title": "Inception", "qualityProfileId": 1, "rootFolderPath": "/movies", "monitored": true, "addOptions": {"searchForMovie": true}}' 'http://localhost:7878/api/v3/movie'
- Search a TV series by name: curl -s -H 'X-Api-Key: YOUR_SONARR_KEY' 'http://localhost:8989/api/v3/series/lookup?term=breaking+bad' | jq '.[0] | {title, year, tvdbId}'
- Add a series: curl -s -X POST -H 'X-Api-Key: YOUR_SONARR_KEY' -H 'Content-Type: application/json' -d '{"tvdbId": 81189, "title": "Breaking Bad", "qualityProfileId": 1, "rootFolderPath": "/tv", "monitored": true, "addOptions": {"searchForMissingEpisodes": true}}' 'http://localhost:8989/api/v3/series'
- View Radarr queue: curl -s -H 'X-Api-Key: YOUR_RADARR_KEY' 'http://localhost:7878/api/v3/queue' | jq '.records[] | {title, status, sizeleft, timeleft}'