diff --git a/docs/API.md b/docs/API.md index f57af6bd7..8a5caadcc 100644 --- a/docs/API.md +++ b/docs/API.md @@ -100,7 +100,7 @@ There is no global error handler, so error bodies are framework defaults. A deni ### Secrets -`GET /api/settings` masks nine secret fields. The per-integration read routes under `/api/settings` **do not mask** and return the real stored values, because the settings forms need them in order to save them again. `GET /api/settings` also leaves `apikey` and `download_client_username` in the clear. +`GET /api/settings` masks ten secret fields. The per-integration read routes under `/api/settings` **do not mask** and return the real stored values, because the settings forms need them in order to save them again. `GET /api/settings` also leaves `apikey` and `download_client_username` in the clear. Two further routes hand over secrets wholesale: `GET /api/notifications/configurations` returns every notification credential unmasked, and `GET /api/settings/database/download` streams the entire database with every secret in plaintext. diff --git a/docs/Configuration.md b/docs/Configuration.md index 439164cec..4ded87cb3 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -225,11 +225,11 @@ When media is removed through Radarr or Sonarr, Maintainerr can remove the match Maintainerr supports qBittorrent and Transmission. Pick the client first, then enter the URL and credentials for that same instance. -| Setting | Description | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Client | Choose `qBittorrent` or `Transmission`. Nothing is selected until you choose. An install that already had a download client when it upgraded keeps `qBittorrent`, the only client that existed before. Changing the client clears the URL field because qBittorrent uses a WebUI base URL while Transmission uses the full RPC endpoint. | -| Delete downloaded data | When enabled, removing a download also deletes its files from disk. Turn this off if you cross-seed, so other torrents that share the data keep working. | -| Fallback seeding ratio | Whether a download has finished seeding is decided by the client's own ratio or time limits first. This fallback ratio only applies when the client enforces no limit, and then only after 23 hours. | +| Setting | Description | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Client | Choose `qBittorrent` or `Transmission`. Nothing is selected until you choose. An install that already had a download client when it upgraded keeps `qBittorrent`, the only client that existed before. Once a client is saved, switching to the other one clears the URL field because qBittorrent uses a WebUI base URL while Transmission uses the full RPC endpoint. | +| Delete downloaded data | When enabled, removing a download also deletes its files from disk. Turn this off if you cross-seed, so other torrents that share the data keep working. | +| Fallback seeding ratio | Whether a download has finished seeding is decided by the client's own ratio or time limits first. This fallback ratio only applies when the client enforces no limit, and then only after 23 hours. | ### qBittorrent diff --git a/docs/MediaServerSwitching.md b/docs/MediaServerSwitching.md index 7c7f6887b..9acc2b775 100644 --- a/docs/MediaServerSwitching.md +++ b/docs/MediaServerSwitching.md @@ -36,6 +36,7 @@ When migrating during a switch: - Compatible rules are automatically converted (application ID and property IDs rewritten) - Incompatible rules are **deleted** from the database (logged with details) +- If a deleted rule was the first in its section, the next rule takes over its section operator, so the section still joins the rest of the group with `AND` or `OR` as before - Rule groups where all rules were incompatible are also deleted - Some properties are **remapped** to their closest equivalent (see [Incompatible Properties](#incompatible-properties)) - Collections are preserved (metadata kept, recreated on new server) diff --git a/docs/Rules.mdx b/docs/Rules.mdx index 27db95f13..0f1c1a5f4 100644 --- a/docs/Rules.mdx +++ b/docs/Rules.mdx @@ -103,13 +103,13 @@ General info about the rule. Some of the information specified here will be show | Custom collection name | The name of the existing manually managed collection to use | | Keep in Maintainerr only | Keep this collection inside Maintainerr and create nothing on your media server. Rules, actions, overlays, and \*arr tags all keep working. Off by default, set per collection, and hidden while `Custom collection` is on. | -Maintainerr only shows `Force reset Seerr record` for movie, show, and season rules. Episode rules always rely on Seerr's normal availability sync because Seerr tracks requests at the season level, not per episode. If an older episode rule still has this option saved, re-saving the rule clears it. +Maintainerr only shows `Force reset Seerr record` for movie, show, and season rules. Episode rules always rely on Seerr's normal availability sync because Seerr tracks requests at the season level, not per episode. If an older episode rule still has this option saved, re-saving the rule clears it. Seerr cannot take one season out of a request either, so on a season rule a request that also covers a season you still have is kept, and the log says so. Maintainerr deletes it when the last of its seasons goes. `Force delete Ombi request` is shown for the same three rule types, for a similar reason: Ombi can only delete a whole request, so deleting one for a single episode would drop the requests for the episodes you still have. Ombi has no availability sync of its own either, so a request left in place stays listed as available even after the media is gone. On a season rule, Maintainerr deletes only the requests that cover nothing but that season; one that also covers another season is kept, and the log says so. Ombi drops a show's own request once its last remaining request is gone. When you choose `Change quality profile and search` for Radarr or Sonarr, Maintainerr shows a quality profile selector for the chosen server. This action does not use `Take action after days`; it changes the item's quality profile immediately when the collection action runs. -The `Tag this content` option keeps a Radarr/Sonarr tag named after the rule group on each matching item for as long as it stays in the collection, and removes it when the item leaves. Because Sonarr tags apply to the whole series, the option is offered for movie collections (Radarr) and whole-show collections (Sonarr) only - not season or episode collections. The tag is created on the \*arr instance if missing, the rule group's name is normalized to the tag character set Radarr/Sonarr accept (lowercase letters, numbers, and hyphens), and tagging is best-effort, so it never blocks a rule run. Renaming a rule group applies the new tag on the next membership change but leaves the previous tag in place until you remove it. +The `Tag this content` option keeps a Radarr/Sonarr tag named after the rule group on each matching item for as long as it stays in the collection, and removes it when the item leaves. Because Sonarr tags apply to the whole series, the option is offered for movie collections (Radarr) and whole-show collections (Sonarr) only - not season or episode collections. The tag is created on the \*arr instance if missing, the rule group's name is normalized to the tag character set Radarr/Sonarr accept (lowercase letters, numbers, and hyphens), and tagging is best-effort, so it never blocks a rule run. Only items the rules added carry the tag, not items you added by hand. Renaming the rule group or switching its \*arr server moves the tag when you save: the old tag comes off and the new one goes on. Rule groups whose names give the same tag on the same \*arr server share it, and an item keeps the tag while any of them still holds it. `Keep in Maintainerr only` is for collections that exist to hold media for review, tag it in the \*arrs, or count down to an action, rather than to be browsed on the media server. Membership, the countdown, actions, overlays, and tags all read Maintainerr's own records, so they behave exactly as they would otherwise. Turning it on tears down a collection Maintainerr already created on the media server; turning it off lets the next rule run recreate it. It cannot be combined with `Custom collection`, which by definition points at a collection that already exists on the server. @@ -171,6 +171,8 @@ Season delete actions unmonitor the season in Sonarr and delete its episode file Episode delete actions share the season folder with other episodes, so the folder is never removed by this action. No leftover folder cleanup is offered for episode rules. +Maintainerr finds the episode in Sonarr by its TVDB episode id when exactly one Sonarr episode has it, so a library numbered differently from Sonarr, such as absolute or DVD order, still acts on the right episode. Otherwise it matches by season and episode number, or by air date. When one file holds several episodes, all of them are unmonitored before the file is deleted, so Sonarr does not download it again. If any of them cannot be unmonitored, the file is kept. + ### Sportarr actions Sportarr runs alongside Sonarr and Radarr; you can configure all three at once. Because Plex reports both TV and sports libraries as the same 'show' type, each collection over a show library names a single manager: pick either Sonarr or Sportarr for that collection (not both). Different collections can use different managers, so the same library can have some collections managed by Sonarr and others by Sportarr. diff --git a/docs/api/logs.md b/docs/api/logs.md index b12c5e98b..9ea5d6ef1 100644 --- a/docs/api/logs.md +++ b/docs/api/logs.md @@ -58,9 +58,7 @@ Response: Only Maintainerr's own daily files are listed. Anything else in the directory is invisible here, including the rotation bookkeeping file. -:::note DATA_DIR is ignored here -In production this route reads `/opt/data/logs` directly and does not honour `DATA_DIR`. If you have moved your data directory, this list will not reflect it. -::: +The files live in the `logs` folder of the data directory, which is `/opt/data` unless `DATA_DIR` points somewhere else. ### `GET /api/logs/files/{file}` diff --git a/docs/api/notifications.md b/docs/api/notifications.md index 93f4c7b12..57ebec16a 100644 --- a/docs/api/notifications.md +++ b/docs/api/notifications.md @@ -118,12 +118,10 @@ Response: ] ``` -| Status | Cause | -| ------------------------ | ---------------------------- | -| `200` | JSON array of configurations | -| `200` with an empty body | The read failed | - -An empty body is not the same as `[]`. `[]` means no agents are configured, an empty body means the read threw. Check the body type to tell them apart. +| Status | Cause | +| ------ | ----------------------------------------------------------------- | +| `200` | JSON array of configurations. `[]` means no agents are configured | +| `500` | `Notification configurations could not be read`: the read failed | Rule group links are not included on this route. diff --git a/docs/api/ombi.md b/docs/api/ombi.md index 5c45e9976..4f031a570 100644 --- a/docs/api/ombi.md +++ b/docs/api/ombi.md @@ -34,7 +34,7 @@ Response: | `200` | Array of usernames, possibly empty | | `400` | `tmdbId` is not an integer, `type` is missing or not `movie`/`tv`, or a number is not one | -Unlike the Seerr route, `type` is required and validated, because Ombi keeps movie and show requests in separate lists and cannot be asked for both at once. +`type` is required and validated, as on the Seerr route, because Ombi keeps movie and show requests in separate lists and cannot be asked for both at once. A request made with Ombi's API key is recorded against Ombi's system user, so the name reported is the alias that request carries instead. diff --git a/docs/api/overlays.md b/docs/api/overlays.md index 2d8414dce..fd3c50d42 100644 --- a/docs/api/overlays.md +++ b/docs/api/overlays.md @@ -602,14 +602,15 @@ Send `multipart/form-data` with a single file field named `font`. Accepted exten | ------ | ------------------------------------------------------------------------------------------------- | | `201` | Stored. The body is the sanitised name and path | | `400` | No file uploaded, an unsupported extension, an invalid filename, or the field is not named `font` | +| `413` | The file exceeds 32 MB | | `500` | The write failed | :::warning Overwrites silently A font with the same sanitised name is **overwritten with no warning and no backup**. Every template referencing that filename renders with the new file on the next run. ::: -:::caution No size limit on this route -Unlike the image route, there is no size cap here and the whole file is buffered in memory. The bytes are also never checked to be a real font. A bad file only shows up later as a warning during a run, after which text falls back to a default typeface. +:::caution The file is not checked to be a font +The whole file is held in memory during the upload, which is why it is capped at 32 MB. The bytes are never checked to be a real font. A bad file only shows up later as a warning during a run, after which text falls back to a default typeface. ::: There is **no delete route for fonts**. An uploaded font can only be removed from disk by hand. diff --git a/docs/api/seerr.md b/docs/api/seerr.md index 64d26237d..cde1cc841 100644 --- a/docs/api/seerr.md +++ b/docs/api/seerr.md @@ -69,6 +69,7 @@ This is the route behind the "Requested by" line in the media modal. Names are r | Parameter | Type | Required | Description | | --------- | -------------- | -------- | ------------------------------------------------------------------------ | | `tmdbId` | path, integer | Yes | TMDB id of the movie or show | +| `type` | query, string | Yes | `movie` or `tv`. Anything else is a `400` | | `season` | query, integer | No | Season number. Only filters show requests, so it is harmless for a movie | Response: @@ -79,10 +80,12 @@ Response: Order is oldest request first. -| Status | Cause | -| ------ | --------------------------------------------------------------------- | -| `200` | Array of usernames, possibly empty | -| `400` | `tmdbId` is not an integer, or `season` is present but not an integer | +| Status | Cause | +| ------ | ------------------------------------------------------------------------------------------------------------ | +| `200` | Array of usernames, possibly empty | +| `400` | `tmdbId` is not an integer, `type` is missing or not `movie`/`tv`, or `season` is present but not an integer | + +`type` is required because TMDB numbers movies and shows separately, so one id can name both a movie and an unrelated show. Only requests of that type are counted. Pass `season` for a season-level lookup. Seerr tracks show requests per season, so without it a season lookup credits whoever requested a _different_ season. diff --git a/docs/api/settings.md b/docs/api/settings.md index f17afa255..56a827112 100644 --- a/docs/api/settings.md +++ b/docs/api/settings.md @@ -11,9 +11,9 @@ See [API conventions](../API.md#api-conventions) for the rules that apply across ## Secrets on this page :::danger Per-integration reads return secrets in cleartext -`GET /api/settings` masks nine secret fields. **Every per-integration read on this page does not.** These routes return the real stored value: +`GET /api/settings` masks ten secret fields. **Every per-integration read on this page does not.** These routes return the real stored value: -`GET /api/settings/jellyfin`, `GET /api/settings/emby`, `GET /api/settings/seerr`, `GET /api/settings/tautulli`, `GET /api/settings/tracearr`, `GET /api/settings/tmdb`, `GET /api/settings/tvdb`, `GET /api/settings/download-client`, and the three `*arr` list routes. +`GET /api/settings/jellyfin`, `GET /api/settings/emby`, `GET /api/settings/seerr`, `GET /api/settings/ombi`, `GET /api/settings/tautulli`, `GET /api/settings/tracearr`, `GET /api/settings/tmdb`, `GET /api/settings/tvdb`, `GET /api/settings/download-client`, and the three `*arr` list routes. `GET /api/settings/database/download` goes further and hands over the entire database, including every secret in plaintext. @@ -48,13 +48,13 @@ Successful `POST` requests answer `201`. `PATCH`, `PUT` and `DELETE` answer `200 This is the backbone read for the whole UI. It returns the full settings row: application title and URL, the Maintainerr API key, the active media server type, every integration's connection fields, both cron schedules, the `*arr` exclusion tag options and the telemetry flag. -| Status | Cause | -| -------------------------- | -------------------------- | -| `200` | The settings | -| `200` with an empty body | No settings row exists yet | -| `200` with `status: "NOK"` | The database read failed | +| Status | Cause | +| ------------------------ | ---------------------------------------------------------- | +| `200` | The settings | +| `200` with an empty body | No settings row exists yet | +| `500` | `The settings could not be read`: the database read failed | -Nine fields are masked: the Plex token, the Jellyfin, Emby, Seerr, TMDB, TVDB, Tautulli and Tracearr API keys, and the download client password. A value of six characters or fewer becomes `****`, anything longer becomes the first three characters, an ellipsis, then the last three. +Ten fields are masked: the Plex token, the Jellyfin, Emby, Seerr, Ombi, TMDB, TVDB, Tautulli and Tracearr API keys, and the download client password. A value of six characters or fewer becomes `****`, anything longer becomes the first three characters, an ellipsis, then the last three. :::caution Two secrets are not masked here `apikey`, the Maintainerr API key itself, and `download_client_username` are returned in the clear. @@ -234,12 +234,12 @@ Afterwards, the media server becomes unavailable to Maintainerr until a new toke **Probe the configured Plex server and return its version.** -| Status | Cause | -| -------------------------- | ------------------------------------------------------------------------------------------------------------------ | -| `200` with `status: "OK"` | Connected. `message` is the Plex version | -| `200` with `status: "NOK"` | `Authenticate with Plex before testing the connection.` when no token is stored, otherwise the bare word `Failure` | +| Status | Cause | +| -------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| `200` with `status: "OK"` | Connected. `message` is the Plex version | +| `200` with `status: "NOK"` | `Authenticate with Plex before testing the connection.` when no token is stored, otherwise the reason the test failed | -This never returns an HTTP error. Note that a `Failure` conflates an unreachable server with a client that was never started, so it gives you little to diagnose with. +This never returns an HTTP error. The test reads the library list and the server identity within five seconds. A failure says why, such as a refused or timed-out connection, or `Plex client is not initialized` when the client was never started. It tests whatever connection the process currently holds, which is not necessarily the hostname you most recently saved. Save first, then test. @@ -300,7 +300,7 @@ You can end up with Jellyfin active alongside stale credentials and collections Saving also re-initialises Streamystats, because it authenticates with the Jellyfin API key. -The connection probe sets no timeout, so this can hang for a while against an unresponsive host. +The connection test before the save gives up after five seconds. ### `POST /api/settings/jellyfin/test` @@ -316,7 +316,7 @@ Takes the same body as the save route. Nothing is stored. Testing is effectively a prerequisite for choosing a user, because the save route validates the user id against this same admin-only list. -Note the message `Invalid API key` is returned for **any** error on the user lookup, not just an authentication failure. +The `NOK` message says why the test failed, such as `Invalid API key` or a refused or timed-out connection. ### `DELETE /api/settings/jellyfin` @@ -496,7 +496,7 @@ Note the alternative way in: `POST` and `PATCH /api/settings` accept `media_serv Radarr, Sonarr and Sportarr are configured as lists of instances, and the four routes for each behave identically. What follows applies to all three. -**The list route** returns every configured instance as `id`, `serverName`, `url` and `apiKey`, with the **API key in cleartext**. It answers `200` even on failure, in which case the body is an error envelope object rather than an array, so check the shape before iterating. +**The list route** returns every configured instance as `id`, `serverName`, `url` and `apiKey`, with the **API key in cleartext**. If the list cannot be read from the database it answers `500` with `The server list could not be read`. **The create and update routes** take `serverName`, `url` and `apiKey`, all required. The URL is **forced to lowercase** when stored, which breaks an instance behind a case-sensitive reverse proxy path. Credentials are stored verbatim and unverified, so test first if you want verification. Duplicate names and URLs are allowed. @@ -510,9 +510,10 @@ The `{id}` in these paths is Maintainerr's own settings row id. **List every configured Radarr instance.** -| Status | Cause | -| ------ | -------------------------------------------------------------------------- | -| `200` | The instances, **with API keys in cleartext**, or an error envelope object | +| Status | Cause | +| ------ | --------------------------------------------- | +| `200` | The instances, **with API keys in cleartext** | +| `500` | The list could not be read | ### `POST /api/settings/radarr` @@ -556,9 +557,10 @@ Nothing cascades: the in-use check is what stops a collection being left pointin **List every configured Sonarr instance.** -| Status | Cause | -| ------ | -------------------------------------------------------------------------- | -| `200` | The instances, **with API keys in cleartext**, or an error envelope object | +| Status | Cause | +| ------ | --------------------------------------------- | +| `200` | The instances, **with API keys in cleartext** | +| `500` | The list could not be read | ### `POST /api/settings/sonarr` @@ -598,9 +600,10 @@ Permanently deletes the instance row, including its API key, with no copy kept. **List every configured Sportarr instance.** -| Status | Cause | -| ------ | -------------------------------------------------------------------------- | -| `200` | The instances, **with API keys in cleartext**, or an error envelope object | +| Status | Cause | +| ------ | --------------------------------------------- | +| `200` | The instances, **with API keys in cleartext** | +| `500` | The list could not be read | ### `POST /api/settings/sportarr` @@ -646,13 +649,13 @@ Permanently deletes the instance row, including its API key, with no copy kept. Takes the same body as the save route, including `serverName`, which the probe does not use. -| Status | Cause | -| -------------------------- | ------------------------------------------------------------------------------------------------- | -| `201` with `status: "OK"` | Connected. `message` is the Radarr version | -| `201` with `status: "NOK"` | `Failure`, or `Unexpected application name returned: ` when a different application answers | -| `400` | Validation failed | +| Status | Cause | +| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `201` with `status: "OK"` | Connected. `message` is the Radarr version | +| `201` with `status: "NOK"` | `Unexpected application name returned: ` when a different application answers, `Failure` when the answer has no version, or why the connection failed | +| `400` | Validation failed | -Real connection failures come back as the bare word `Failure` with no detail, because the underlying error is swallowed before it can be classified. +A connection failure says why, such as `Invalid API key` or a refused or timed-out connection. The test gives up after five seconds. Testing is not a precondition for saving. @@ -660,11 +663,11 @@ Testing is not a precondition for saving. **Probe a Sonarr connection using credentials in the body, without saving.** -| Status | Cause | -| -------------------------- | -------------------------------------------- | -| `201` with `status: "OK"` | Connected. `message` is the Sonarr version | -| `201` with `status: "NOK"` | `Failure`, or an unexpected application name | -| `400` | Validation failed | +| Status | Cause | +| -------------------------- | ----------------------------------------------------------------------- | +| `201` with `status: "OK"` | Connected. `message` is the Sonarr version | +| `201` with `status: "NOK"` | An unexpected application name, `Failure`, or why the connection failed | +| `400` | Validation failed | A Radarr behind the URL is rejected by name. @@ -672,11 +675,11 @@ A Radarr behind the URL is rejected by name. **Probe a Sportarr connection and enforce the minimum supported version, without saving.** -| Status | Cause | -| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| `201` with `status: "OK"` | Connected. `message` is the Sportarr version | -| `201` with `status: "NOK"` | `Failure`, an unexpected application name, or `Sportarr is below the minimum supported version 4.0.1022. Please update Sportarr.` | -| `400` | Validation failed | +| Status | Cause | +| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `201` with `status: "OK"` | Connected. `message` is the Sportarr version | +| `201` with `status: "NOK"` | An unexpected application name, `Sportarr is below the minimum supported version 4.0.1022. Please update Sportarr.`, `Failure`, or why the connection failed | +| `400` | Validation failed | This is the only place the version requirement is applied. A build whose version cannot be parsed passes the check. @@ -738,6 +741,8 @@ Also answers on the `/api/settings/test/overseerr` and `/api/settings/test/jelly | `201` with `status: "NOK"` | `Failure, an unexpected response was returned. The URL is likely incorrect.`, or a classified connection failure such as `Invalid API key` | | `400` | Validation failed | +An Ombi URL fails with the unexpected-response message. Ombi answers the same path with any key, so the test also checks for a field only Seerr sends. + There is no "test what is currently stored" mode. Both fields are required in the body. :::caution This sends your credentials to whatever host you name @@ -907,18 +912,20 @@ Rules that read Streamystats values stop resolving rather than being removed. ### `POST /api/settings/test/streamystats` -**Probe a Streamystats URL, without saving or sending any credential.** +**Probe a Streamystats URL with the stored Jellyfin API key, without saving.** -| Status | Cause | -| -------------------------- | ------------------------------------------------------------------------------------------------------------------ | -| `201` with `status: "OK"` | Connected. `message` is the Streamystats version | -| `201` with `status: "NOK"` | `Unexpected response from Streamystats. Verify the URL points to a Streamystats instance.` or a connection failure | -| `400` | Validation failed | -| `403` | The active media server is not Jellyfin | +| Status | Cause | +| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `201` with `status: "OK"` | Connected. `message` is the Streamystats version | +| `201` with `status: "NOK"` | `Unexpected response from Streamystats. Verify the URL points to a Streamystats instance.`, `Unexpected response`, `API key is required` when no Jellyfin key is stored, `Invalid API key`, or a connection failure | +| `400` | Validation failed | +| `403` | The active media server is not Jellyfin | -This is the only test route that deliberately **withholds** a stored credential from the URL you supply, so it cannot be used to leak your Jellyfin key to an arbitrary host. +The probe reads the Streamystats version, then reads its watchlists with your stored Jellyfin API key, the same way the live client does. A pass therefore means the key works against that Streamystats too. -A pass here does not prove the integration will work, because the live client also needs the Jellyfin API key, which this probe never exercises. +:::caution This sends your stored Jellyfin key to the host you name +The URL comes from the request body, but the key is the one stored in Maintainerr. It is sent only once the host has answered the version check like a Streamystats instance. Even so, anyone who can reach the port can point this route at a host they control and receive your Jellyfin key. +::: ### `GET /api/settings/tracearr` @@ -1044,7 +1051,7 @@ Deleting the TVDB key later does not reset this value either. The key is checked against TMDB **before** anything is written, so a wrong key cannot overwrite a working one. :::caution Posting an empty key silently reverts to the bundled key -An empty string passes validation, and the check then falls back to the currently loaded key, which normally passes. The empty string is stored, `Success` is reported, and the running client drops back to the shared key that ships with Maintainerr. +An empty string passes validation, and the check then tests the shared key that ships with Maintainerr, which normally passes. The empty string is stored, `Success` is reported, and the running client drops back to that shared key. The effect is the same as calling `DELETE`. Use `DELETE` if that is what you want. ::: @@ -1079,19 +1086,15 @@ The cached TMDB responses are not flushed. Use `POST /api/settings/metadata/refr **Validate a TVDB API key and store it if it works.** -| Status | Cause | -| -------------------------- | ----------------------------------------------------------------------------------------------- | -| `201` with `status: "OK"` | Stored | -| `201` with `status: "NOK"` | `Invalid API key`, `No TVDB API key configured`, `Unexpected response`, or a connection failure | -| `400` | The field is missing or not a string | +| Status | Cause | +| -------------------------- | ---------------------------------------------------------------------------------------- | +| `201` with `status: "OK"` | Stored | +| `201` with `status: "NOK"` | `Invalid API key`, `API key is required`, `Unexpected response`, or a connection failure | +| `400` | The field is missing or not a string | -Validated before saving, so a bad key never overwrites a good one. +Validated before saving, so a bad key never overwrites a good one. The check logs in to TVDB and then reads one series, so a key that can log in but not read metadata is refused too. -:::danger Posting an empty key wipes your configured key -This is worse than the TMDB case. An empty string passes validation, the check falls back to the **already stored** key and passes, and the empty string is then stored. - -`Success` is reported while your TVDB key is gone and TVDB is left unauthenticated, exactly as if you had called `DELETE`. Unlike TMDB there is no bundled fallback key. Use `DELETE` if that is what you want. -::: +An empty key is refused with `API key is required` and nothing is stored. Use `DELETE` to remove the key. ### `DELETE /api/settings/tvdb` @@ -1114,25 +1117,25 @@ Cached TVDB responses are not flushed. **Test a TMDB API key, without saving it.** -| Status | Cause | -| -------------------------- | ----------------------------------------------------------------------------------------------- | -| `201` with `status: "OK"` | The key works | -| `201` with `status: "NOK"` | `Invalid API key`, `No TMDB API key configured`, `Unexpected response`, or a connection failure | -| `400` | The field is missing or not a string | +| Status | Cause | +| -------------------------- | ----------------------------------------------------------------- | +| `201` with `status: "OK"` | The key works | +| `201` with `status: "NOK"` | `Invalid API key`, `Unexpected response`, or a connection failure | +| `400` | The field is missing or not a string | -An empty key re-tests whatever is currently loaded rather than reporting that nothing is configured. +An empty key tests the shared key that ships with Maintainerr, which is the key an empty setting falls back to. ### `POST /api/settings/test/tvdb` **Test a TVDB API key, without saving it.** -| Status | Cause | -| -------------------------- | ----------------------------------------------------------------------------------------------- | -| `201` with `status: "OK"` | The key works | -| `201` with `status: "NOK"` | `Invalid API key`, `No TVDB API key configured`, `Unexpected response`, or a connection failure | -| `400` | The field is missing or not a string | +| Status | Cause | +| -------------------------- | ---------------------------------------------------------------------------------------- | +| `201` with `status: "OK"` | The key works | +| `201` with `status: "NOK"` | `Invalid API key`, `API key is required`, `Unexpected response`, or a connection failure | +| `400` | The field is missing or not a string | -An empty key re-tests the already stored key, and only reports that nothing is configured when nothing is stored either. +An empty key is refused with `API key is required`. It does not fall back to the stored key. ### `POST /api/settings/metadata/refresh/{provider}` @@ -1150,7 +1153,7 @@ No request body is read. | `201` with `status: "NOK"` | The provider's connection test failed, or something threw | | `400` | The provider is not one of the three | -The connection is tested first using whatever is **already configured**, not anything from the request. TVDB reports `No TVDB API key configured` and does nothing when no key is stored. +The connection is tested first using whatever is **already configured**, not anything from the request. TVDB reports `API key is required` and does nothing when no key is stored. The refresh itself is fire and forget. Only its start is reported, and per-item failures are logged rather than returned. diff --git a/docs/api/streamystats.md b/docs/api/streamystats.md index a9a77514b..e6507949b 100644 --- a/docs/api/streamystats.md +++ b/docs/api/streamystats.md @@ -106,4 +106,4 @@ Response: The second `404` covers four different situations: the Streamystats server id could not be resolved, Streamystats does not know the item, the request to Streamystats failed or timed out, and the payload failed validation. A `404` is therefore not proof that the item has no watch history. -Successful responses are cached in memory for 20 minutes, keyed by item and server id. A transport failure is never cached and is retried on the next request, but a response that fails validation is cached, so that item keeps answering `404` for the full 20 minutes. +The statistics are read from Streamystats on every request and never cached, so reopening an item after a play shows the new numbers. diff --git a/docs/api/watch-statistics.md b/docs/api/watch-statistics.md index b677119e7..ac92d4caa 100644 --- a/docs/api/watch-statistics.md +++ b/docs/api/watch-statistics.md @@ -70,6 +70,8 @@ The per-user totals come from Tautulli's `get_item_user_stats`. Those rows carry Works with Plex, Jellyfin and Emby. Only the Tracearr server bound in your settings is read, so plays recorded against another server are not counted. +Before reading, Maintainerr checks that this Tracearr server tracks your media server, the same check rule runs make. Plex rating keys repeat across servers, so without it another server's plays could be shown. If the check does not pass, the route answers `502` and waits a minute before checking again. A server that passes is kept until the Tracearr or media server settings change. + | Parameter | Type | Required | Description | | --------- | ------ | -------- | --------------------------------------------------------------------------- | | `itemId` | string | Yes | Media server item id, the same id used by `GET /api/media-server/meta/{id}` | diff --git a/static/openapi-spec/maintainerr_api_specs.yaml b/static/openapi-spec/maintainerr_api_specs.yaml index 6dc167dc1..b92ab2cbc 100644 --- a/static/openapi-spec/maintainerr_api_specs.yaml +++ b/static/openapi-spec/maintainerr_api_specs.yaml @@ -1819,6 +1819,14 @@ paths: in: path schema: type: number + - name: type + required: true + in: query + schema: + enum: + - movie + - tv + type: string - name: season required: false in: query @@ -1844,6 +1852,14 @@ paths: in: path schema: type: number + - name: type + required: true + in: query + schema: + enum: + - movie + - tv + type: string - name: season required: false in: query @@ -1869,6 +1885,14 @@ paths: in: path schema: type: number + - name: type + required: true + in: query + schema: + enum: + - movie + - tv + type: string - name: season required: false in: query