This guide provides comprehensive documentation for using the TsArr TypeScript SDK to interact with Radarr, Sonarr, Lidarr, Readarr, Prowlarr, Bazarr, qBittorrent, Seerr and Jellyfin.
bun add tsarrAll clients follow the same initialization pattern:
import { RadarrClient, SonarrClient, LidarrClient, ReadarrClient, ProwlarrClient, QBittorrentClient, SeerrClient, JellyfinClient } from 'tsarr';
// Initialize a client
const radarr = new RadarrClient({
baseUrl: 'http://localhost:7878',
apiKey: 'your-radarr-api-key'
});
const sonarr = new SonarrClient({
baseUrl: 'http://localhost:8989',
apiKey: 'your-sonarr-api-key'
});
// qBittorrent uses username/password instead of API keys
const qbit = new QBittorrentClient({
baseUrl: 'http://localhost:8080',
username: 'admin',
password: 'adminadmin'
});
// Seerr (also works with Jellyseerr and Overseerr)
const seerr = new SeerrClient({
baseUrl: 'http://localhost:5055',
apiKey: 'your-seerr-api-key'
});
// Jellyfin — API key from Dashboard -> Advanced -> API Keys
const jellyfin = new JellyfinClient({
baseUrl: 'http://localhost:8096',
apiKey: 'your-jellyfin-api-key'
});Note: Jellyfin serves PascalCase JSON (
Id,Name,Items), unlike the camelCase used by the Servarr services. The generated types reflect this.
For security, use environment variables for API keys:
const radarr = new RadarrClient({
baseUrl: process.env.RADARR_BASE_URL || 'http://localhost:7878',
apiKey: process.env.RADARR_API_KEY!
});// Get system status
const status = await radarr.getSystemStatus();
console.log(`Version: ${status.data?.version}`);
// Health check
const health = await radarr.getHealth();
console.log(`Health issues: ${health.data?.length || 0}`);// Get all movies
const movies = await radarr.getMovies();
// Get specific movie
const movie = await radarr.getMovieById(123);
// Search for movies
const searchResults = await radarr.searchMovies('Inception');
// Add a movie
const newMovie = await radarr.addMovie({
title: 'The Matrix',
year: 1999,
tmdbId: 603,
qualityProfileId: 1,
monitored: true,
rootFolderPath: '/media/movies'
});
// Update movie
const updatedMovie = await radarr.updateMovie(movieId, {
monitored: false
});
// Delete movie
await radarr.deleteMovie(movieId, { deleteFiles: true });// Get all series
const series = await sonarr.getSeries();
// Get specific series
const show = await sonarr.getSeriesById(123);
// Search for series
const searchResults = await sonarr.searchSeries('Breaking Bad');
// Add series
const newSeries = await sonarr.addSeries({
title: 'Breaking Bad',
tvdbId: 81189,
qualityProfileId: 1,
monitored: true,
rootFolderPath: '/media/tv'
});// Get download queue
const queue = await radarr.getQueue();
// Remove item from queue
await radarr.deleteQueueItem(queueId);
// Search releases for a movie
const releases = await radarr.getRelease(movieId);
// Grab/push a release
await radarr.addRelease(release);// Inspect profiles before adding an artist
const metadataProfiles = await lidarr.getMetadataProfiles();
const metadataProfile = await lidarr.getMetadataProfile(4);
// Search complete release resources for one album
const albumCandidates = await lidarr.getRelease(albumId);
// Or search all monitored albums for one artist
const artistCandidates = await lidarr.getRelease(undefined, artistId);
// Post one candidate back unchanged to grab it
const candidate = albumCandidates.data?.[0];
if (candidate) {
await lidarr.addRelease(candidate);
}// Get quality profiles
const profiles = await radarr.getQualityProfiles();
// Get quality definitions
const definitions = await radarr.getQualityDefinitions();
// Update quality definition
await radarr.updateQualityDefinition(defId, {
minSize: 1000,
maxSize: 5000
});Each service exposes file management methods for its media type:
// Radarr - Movie files
const movieFiles = await radarr.getMovieFiles([movieId]);
const movieFile = await radarr.getMovieFile(fileId);
await radarr.deleteMovieFile(fileId);
// Sonarr - Episode files
const episodeFiles = await sonarr.getEpisodeFiles(seriesId);
const episodeFile = await sonarr.getEpisodeFile(fileId);
await sonarr.deleteEpisodeFile(fileId);
// Lidarr - Track files
const trackFiles = await lidarr.getTrackFiles(artistId);
const trackFile = await lidarr.getTrackFile(fileId);
await lidarr.deleteTrackFile(fileId);
// Readarr - Book files
const bookFiles = await readarr.getBookFiles(authorId);
const bookFile = await readarr.getBookFile(fileId);
await readarr.deleteBookFile(fileId);TsArr provides specific error types for different scenarios:
import { ApiKeyError, ConnectionError, NotFoundError, ValidationError } from 'tsarr';
try {
const movies = await radarr.getMovies();
} catch (error) {
if (error instanceof ApiKeyError) {
console.error('Invalid API key');
} else if (error instanceof ConnectionError) {
console.error('Could not connect to Radarr');
} else if (error instanceof NotFoundError) {
console.error('Resource not found');
} else if (error instanceof ValidationError) {
console.error('Invalid request data');
}
}// Import multiple movies
const importResults = await radarr.importMovies([
{ path: '/path/to/movie1.mkv', movieId: 1 },
{ path: '/path/to/movie2.mkv', movieId: 2 }
]);
// Delete multiple movies
await radarr.deleteMovies([1, 2, 3], { deleteFiles: true });
// Update multiple series
await sonarr.updateSeries([
{ id: 1, monitored: false },
{ id: 2, monitored: true }
]);// Search with filters
const recentMovies = await radarr.getMovies({
sortKey: 'dateAdded',
sortDirection: 'descending',
page: 1,
pageSize: 50
});
// Filter by status
const monitoredMovies = await radarr.getMovies({
monitored: true
});// Get download client settings
const downloadClients = await radarr.getDownloadClients();
// Get indexer settings
const indexers = await radarr.getIndexers();
// Update naming configuration
await radarr.updateNamingConfig({
renameMovies: true,
movieFolderFormat: '{Movie Title} ({Release Year})'
});All API responses are fully typed. Use TypeScript's intellisense for available properties:
const movie = await radarr.getMovieById(123);
// movie.data is typed with all available properties
console.log(movie.data?.title); // ✅ Type-safe
console.log(movie.data?.invalidProp); // ❌ TypeScript errorAll client methods return promises and work with both async/await and .then():
// Async/await (recommended)
const movies = await radarr.getMovies();
// Promise chains
radarr.getMovies()
.then(response => console.log(response.data))
.catch(error => console.error(error));TsArr uses Bun's native fetch API which handles connection pooling automatically.
Be mindful of API rate limits. For bulk operations, add delays:
for (const movie of movies) {
await processMovie(movie);
await new Promise(resolve => setTimeout(resolve, 100)); // 100ms delay
}For large libraries, use pagination:
let page = 1;
let allMovies = [];
while (true) {
const response = await radarr.getMovies({ page, pageSize: 100 });
if (!response.data?.length) break;
allMovies.push(...response.data);
page++;
}// Production instances
const prodRadarr = new RadarrClient({
baseUrl: 'https://radarr.example.com',
apiKey: process.env.PROD_RADARR_API_KEY!
});
const prodSonarr = new SonarrClient({
baseUrl: 'https://sonarr.example.com',
apiKey: process.env.PROD_SONARR_API_KEY!
});
// Development instances
const devRadarr = new RadarrClient({
baseUrl: 'http://localhost:7878',
apiKey: process.env.DEV_RADARR_API_KEY!
});Every client owns its configuration, so instances of the same service never
interfere with each other — prodRadarr keeps its base URL and API key when
devRadarr is constructed. The same holds for updateConfig() on one instance.
const radarr = new RadarrClient({
baseUrl: 'http://localhost:7878',
apiKey: 'your-api-key',
// Custom fetch options are passed through
timeout: 30000,
headers: {
'User-Agent': 'MyApp/1.0'
}
});- See Examples for real-world automation scripts
- Check the Examples Directory for runnable code
- Explore the API Documentation for complete method reference