Sonarr/Sonarr · warning · BadRequestException

seriesId or episodeIds must be provided

Error message

seriesId or episodeIds must be provided

What it means

Returned by GET /api/v3/episode when none of seriesId, episodeIds, or episodeFileId are supplied. The endpoint requires a scope to query episodes; with no scope it falls through every conditional branch and throws BadRequestException (HTTP 400). It prevents an expensive unbounded episode query.

Source

Thrown at src/Sonarr.Api.V3/Episodes/EpisodeController.cs:48

            if (seriesId.HasValue)
            {
                if (seasonNumber.HasValue)
                {
                    return MapToResource(_episodeService.GetEpisodesBySeason(seriesId.Value, seasonNumber.Value), includeSeries, includeEpisodeFile, includeImages);
                }

                return MapToResource(_episodeService.GetEpisodeBySeries(seriesId.Value), includeSeries, includeEpisodeFile, includeImages);
            }
            else if (episodeIds.Any())
            {
                return MapToResource(_episodeService.GetEpisodes(episodeIds), includeSeries, includeEpisodeFile, includeImages);
            }
            else if (episodeFileId.HasValue)
            {
                return MapToResource(_episodeService.GetEpisodesByFileId(episodeFileId.Value), includeSeries, includeEpisodeFile, includeImages);
            }

            throw new BadRequestException("seriesId or episodeIds must be provided");
        }

        [RestPutById]
        [Consumes("application/json")]
        public ActionResult<EpisodeResource> SetEpisodeMonitored([FromRoute] int id, [FromBody] EpisodeResource resource)
        {
            _episodeService.SetEpisodeMonitored(id, resource.Monitored);

            resource = MapToResource(_episodeService.GetEpisode(id), false, false, false);

            return Accepted(resource);
        }

        [HttpPut("monitor")]
        [Consumes("application/json")]
        public IActionResult SetEpisodesMonitored([FromBody] EpisodesMonitoredResource resource, [FromQuery] bool includeImages = false)
        {
            if (resource.EpisodeIds.Count == 1)

View on GitHub (pinned to da2284d7ea)

Solutions

  1. Supply ?seriesId=<id> for all episodes of a series.
  2. Or ?episodeIds=1&episodeIds=2 for specific episodes.
  3. Or ?episodeFileId=<id> for episodes tied to a file.
  4. Validate that at least one filter is non-empty in the client before sending.

Example fix

// before
fetch('/api/v3/episode')

// after
fetch('/api/v3/episode?seriesId=' + seriesId)
Defensive patterns

Strategy: validation

Validate before calling

const params = new URLSearchParams();
if (seriesId) params.set('seriesId', seriesId);
if (episodeIds?.length) episodeIds.forEach(id => params.append('episodeIds', id));
if (episodeFileId) params.set('episodeFileId', episodeFileId);
if (![...params.keys()].length) throw new Error('seriesId, episodeIds, or episodeFileId required');
fetch('/api/v3/episode?' + params);

Type guard

function hasEpisodeScope(p){ return p.seriesId != null || (Array.isArray(p.episodeIds) && p.episodeIds.length>0) || p.episodeFileId != null; }

Prevention

When it happens

Trigger: GET /api/v3/episode with an empty query string, or with parameters that don't match any of the three supported filter names. Sending episodeIds=[] alongside no seriesId also reaches this throw.

Common situations: A client defaulting all filters to null/empty on initial load; a typo in a query parameter name (e.g. episodeId instead of episodeIds); an integration test hitting the bare endpoint.

Related errors


AI-assisted analysis of Sonarr/Sonarr@da2284d7ea (2026-08-13). Data as JSON: /api/errors/9a648e85fb898947. Report an issue: GitHub.