From bbb22620b0b106f5c8f5f23cc78bb7c68a4816dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Sep 2026 12:09:12 +0000 Subject: [PATCH 1/2] Initial plan From 61eae054729ac78176c51939414ab41096dbe2a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Sep 2026 12:15:54 +0000 Subject: [PATCH 2/2] docs: cover post-delete rescans and media item path Co-authored-by: enoch85 <4511254+enoch85@users.noreply.github.com> --- docs/Collections.md | 1 + docs/api/media-server.md | 26 ++++++++++++++++--- .../openapi-spec/maintainerr_api_specs.yaml | 5 ++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/docs/Collections.md b/docs/Collections.md index 84ca3a08f..59747916f 100644 --- a/docs/Collections.md +++ b/docs/Collections.md @@ -15,6 +15,7 @@ This runs each collection's configured action (such as delete, unmonitor, or do If a rule-managed item is still in the collection but its most recent rule evaluation failed, Maintainerr skips the automatic handling action for that item until the rule can be evaluated cleanly again. Manually added items are still eligible for handling, unless an exclusion covers them. That means an exclusion for this collection or a global one, and excluding a show or season covers everything inside it. When a delete-style action removes files, Maintainerr also prunes that media from any other Maintainerr-managed collections that still list it. This prevents already-deleted items from being re-processed while Jellyfin or Emby are still catching up on their next library scan. + After a Radarr, Sonarr, or Sportarr delete that removes files, Maintainerr also asks the media server to rescan that item's folder so the orphan disappears sooner. This uses the media server's own path, so unlike leftover-folder cleanup it does not need a matching bind mount or path mapping in the Maintainerr container. If eligible media is actively being streamed, Maintainerr defers it to the next collection-handler run instead of acting on it mid-playback. This is a best-effort snapshot taken once per run, so playback that starts later is only protected on the following pass. ::: diff --git a/docs/api/media-server.md b/docs/api/media-server.md index a51072930..5e4b2d17a 100644 --- a/docs/api/media-server.md +++ b/docs/api/media-server.md @@ -137,7 +137,7 @@ Response: } ``` -Items carry the full media item shape. On top of the media server's own fields, this route adds Maintainerr state: `maintainerrExclusionId`, `maintainerrExclusionType` which is `specific` or `global`, `maintainerrIsManual`, and `maintainerrCollections`, the titles of every collection the item belongs to. +Items carry the full media item shape except `path`, which is only populated by the single-item metadata route. On top of the media server's own fields, this route adds Maintainerr state: `maintainerrExclusionId`, `maintainerrExclusionType` which is `specific` or `global`, `maintainerrIsManual`, and `maintainerrCollections`, the titles of every collection the item belongs to. | Status | Cause | | ------ | ------------------------------------------------------------------------------------------- | @@ -166,7 +166,7 @@ Note that `limit` has no cap and is passed straight through. A `limit` of `0` re | `query` | path | Yes | Search text. Taken raw from the path, so a query containing `/` will not route | | `type` | query | No | `movie`, `show`, `season` or `episode`. Not validated: an unrecognised value silently means `movie` | -Returns an array of media items with the same Maintainerr enrichment as the content route. Season and episode results also carry `parentItem`, the parent or grandparent's metadata. +Returns an array of media items with the same Maintainerr enrichment as the content route. `path` is not populated here either. Season and episode results also carry `parentItem`, the parent or grandparent's metadata. | Status | Cause | | ------ | --------------------------------------------------------------------- | @@ -186,7 +186,7 @@ Search behaviour differs by backend. Plex matches on title as a prefix filter, w | `id` | path | Yes | Library id | | `limit` | query, integer | No | Maximum items. There is no default from Maintainerr, so each backend applies its own | -Returns raw media items with no Maintainerr enrichment. +Returns raw media items with no Maintainerr enrichment. `path` is not populated here. | Status | Cause | | ------ | --------------------------------- | @@ -257,12 +257,30 @@ Result kinds differ by backend. Plex filters to movies and shows, so episodes ne | --------- | ---- | -------- | --------------------------------------------------------- | | `id` | path | Yes | Item id. A Plex rating key, or a Jellyfin or Emby item id | +Response: + +```json +{ + "id": "12345", + "title": "An example title", + "guid": "plex://movie/abc", + "type": "movie", + "addedAt": "2026-01-01T00:00:00.000Z", + "providerIds": { "tmdb": ["550"] }, + "mediaSources": [{ "id": "1", "sizeBytes": 0 }], + "path": "/media/movies/An example title (2026)/An example title (2026).mkv", + "library": { "id": "1", "title": "Movies" } +} +``` + | Status | Cause | | ------ | ---------------------------------------------------------------------------- | | `200` | The item, **or an empty body for both "no such item" and "the read failed"** | There is no way to tell a missing item from a failed read here. +`path` is the server's own filesystem path for that one item: movies and episodes return the media file, while shows and seasons return the folder. Only this single-item route populates it, and a Plex season has no path of its own. + :::caution Watch fields are per-user and cached `viewCount`, `lastViewedAt` and `userRating` are scoped to the single Jellyfin or Emby user Maintainerr is configured with, and are cached for 5 minutes. Do not use them to drive watch or deletion decisions. Use `GET /api/media-server/meta/{id}/seen` for that. ::: @@ -285,7 +303,7 @@ On Plex the hierarchy is unambiguous and this works for both shows and seasons. On Jellyfin and Emby it asks for items whose parent is the id you gave. A season's parent there is the **library folder**, not the show, so asking a series for its seasons does not reliably return them. Maintainerr uses a dedicated seasons lookup internally for that, and this route does not reach it. ::: -Unaired placeholder episodes are not filtered out on this route. Emby caps the read at 500 rows. +Unaired placeholder episodes are not filtered out on this route. `path` is not guaranteed here. Emby caps the read at 500 rows. ### `GET /api/media-server/meta/{id}/seen` diff --git a/static/openapi-spec/maintainerr_api_specs.yaml b/static/openapi-spec/maintainerr_api_specs.yaml index e7357af46..7f4672158 100644 --- a/static/openapi-spec/maintainerr_api_specs.yaml +++ b/static/openapi-spec/maintainerr_api_specs.yaml @@ -1383,6 +1383,11 @@ paths: application/json: schema: type: object + description: Single-item media metadata. Only this route populates `path`, which is the media server's own filesystem path for the item. + properties: + path: + type: string + description: Server-side filesystem path for the item. Movies and episodes return the media file, while shows and seasons return the folder. Plex seasons do not have a path. tags: - MediaServer /api/media-server/meta/{id}/maintainerr-status: