Download OpenAPI specification:
Artist metadata and image management for Emby, Jellyfin, and Kodi.
Authenticates a user and sets a session cookie. Behavior depends on the instance-level auth.method setting: for "local", validates against the local user database; for "emby" or "jellyfin", authenticates against the configured media server via its AuthenticateByName API.
| username required | string |
| password required | string |
{- "username": "string",
- "password": "string"
}{- "status": "string"
}Initiates the OpenID Connect authorization code flow with PKCE. Generates state, nonce, and code verifier parameters, stores them in HTTP-only cookies, and redirects the user to the configured identity provider's authorization endpoint. The IdP will redirect back to the callback endpoint after authentication.
{- "error": "string",
- "details": [
- "string"
]
}Handles the identity provider's redirect after authentication. Validates the state parameter against the stored cookie, exchanges the authorization code for tokens using PKCE, verifies the ID token and nonce, then runs the standard login/provisioning flow (user lookup, auto-provision if enabled, session creation). On success, sets a session cookie and redirects to the application root.
| code required | string Authorization code from the identity provider |
| state required | string State parameter for CSRF validation |
| error | string Error code returned by the identity provider (e.g. access_denied) |
| error_description | string Human-readable error description from the identity provider |
Creates the initial administrator account during first-time setup. Supports three auth methods: "local" creates a username/password account, "emby" or "jellyfin" authenticates against a media server and auto-creates the first server connection.
| auth_method | string Default: "local" Enum: "local" "emby" "jellyfin" Authentication method for this instance |
| username required | string |
| password required | string Must be at least 8 characters for local auth |
| server_url | string <uri> Media server URL, required when auth_method is emby or jellyfin |
{- "auth_method": "local",
- "username": "string",
- "password": "string",
}{- "status": "string"
}| name required | string |
| scopes | string Default: "read" Comma-separated scopes (default "read") |
{- "name": "string",
- "scopes": "read"
}{- "id": "string",
- "token": "string",
- "name": "string"
}[- {
- "id": "string",
- "name": "string",
- "scopes": "string",
- "user_id": "string",
- "status": "active",
- "created_at": "2019-08-24T14:15:22Z",
- "last_used_at": "2019-08-24T14:15:22Z",
- "revoked_at": "2019-08-24T14:15:22Z"
}
]Permanently removes a revoked token. Anonymizes associated audit log entries. Cannot delete active tokens.
| id required | string |
{- "error": "string",
- "details": [
- "string"
]
}| page | integer Default: 1 |
| page_size | integer Default: 50 |
| sort | string |
| order | string Enum: "asc" "desc" |
| search | string |
| filter | string |
| ids | string Comma-separated list of artist IDs to restrict the result to. Used by the off-page bulk-selection "Show selected" affordance (#1227). Tokens that do not match the canonical ID shape (^[A-Za-z0-9_-]{1,64}$) are dropped server-side. The slice is capped at 1000 (artist.MaxListIDs) so the chip cannot exceed the bulk-action endpoint's hard limit. |
| filter_missing_meta | string Enum: "+y" "-y" Include (+y) or exclude (-y) artists with missing metadata |
| filter_missing_images | string Enum: "+y" "-y" Include (+y) or exclude (-y) artists with missing images |
| filter_missing_mbid | string Enum: "+y" "-y" Include (+y) or exclude (-y) artists with no MusicBrainz ID |
| filter_excluded | string Enum: "+y" "-y" Include (+y) or exclude (-y) artists marked as excluded |
| filter_locked | string Enum: "+y" "-y" Include (+y) or exclude (-y) artists that are locked |
| filter_type_person | string Enum: "+y" "-y" Include (+y) or exclude (-y) artists of type "person" |
| filter_type_group | string Enum: "+y" "-y" Include (+y) or exclude (-y) artists of type "group" |
| filter_type_orchestra | string Enum: "+y" "-y" Include (+y) or exclude (-y) artists of type "orchestra" |
| library_id | string Filter artists by library ID |
| filter_library_{id} | string Enum: "+y" "-y" Per-library include/exclude filter. Replace {id} with a library ID. Use +y to include only artists from that library, -y to exclude them. Multiple filter_library_{id} params may be combined. |
{- "artists": [
- {
- "id": "string",
- "name": "string",
- "sort_name": "string",
- "type": "string",
- "gender": "string",
- "origin": "string",
- "disambiguation": "string",
- "musicbrainz_id": "string",
- "audiodb_id": "string",
- "discogs_id": "string",
- "wikidata_id": "string",
- "deezer_id": "string",
- "spotify_id": "string",
- "genres": [
- "string"
], - "styles": [
- "string"
], - "moods": [
- "string"
], - "years_active": "string",
- "born": "string",
- "formed": "string",
- "died": "string",
- "disbanded": "string",
- "biography": "string",
- "path": "string",
- "library_id": "string",
- "nfo_exists": true,
- "thumb_exists": true,
- "fanart_exists": true,
- "fanart_count": 0,
- "logo_exists": true,
- "banner_exists": true,
- "thumb_low_res": true,
- "fanart_low_res": true,
- "logo_low_res": true,
- "banner_low_res": true,
- "thumb_placeholder": "string",
- "fanart_placeholder": "string",
- "logo_placeholder": "string",
- "banner_placeholder": "string",
- "health_score": 0.1,
- "is_excluded": true,
- "exclusion_reason": "string",
- "is_classical": true,
- "locked": true,
- "lock_source": "string",
- "locked_at": "2019-08-24T14:15:22Z",
- "locked_fields": [
- "string"
], - "audiodb_id_fetched_at": "2019-08-24T14:15:22Z",
- "discogs_id_fetched_at": "2019-08-24T14:15:22Z",
- "wikidata_id_fetched_at": "2019-08-24T14:15:22Z",
- "lastfm_fetched_at": "2019-08-24T14:15:22Z",
- "metadata_sources": {
- "property1": "string",
- "property2": "string"
}, - "last_scanned_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "total": 0,
- "page": 0,
- "page_size": 0
}[- {
- "artists": [
- {
- "id": "string",
- "name": "string",
- "sort_name": "string",
- "type": "string",
- "gender": "string",
- "origin": "string",
- "disambiguation": "string",
- "musicbrainz_id": "string",
- "audiodb_id": "string",
- "discogs_id": "string",
- "wikidata_id": "string",
- "deezer_id": "string",
- "spotify_id": "string",
- "genres": [
- "string"
], - "styles": [
- "string"
], - "moods": [
- "string"
], - "years_active": "string",
- "born": "string",
- "formed": "string",
- "died": "string",
- "disbanded": "string",
- "biography": "string",
- "path": "string",
- "library_id": "string",
- "nfo_exists": true,
- "thumb_exists": true,
- "fanart_exists": true,
- "fanart_count": 0,
- "logo_exists": true,
- "banner_exists": true,
- "thumb_low_res": true,
- "fanart_low_res": true,
- "logo_low_res": true,
- "banner_low_res": true,
- "thumb_placeholder": "string",
- "fanart_placeholder": "string",
- "logo_placeholder": "string",
- "banner_placeholder": "string",
- "health_score": 0.1,
- "is_excluded": true,
- "exclusion_reason": "string",
- "is_classical": true,
- "locked": true,
- "lock_source": "string",
- "locked_at": "2019-08-24T14:15:22Z",
- "locked_fields": [
- "string"
], - "audiodb_id_fetched_at": "2019-08-24T14:15:22Z",
- "discogs_id_fetched_at": "2019-08-24T14:15:22Z",
- "wikidata_id_fetched_at": "2019-08-24T14:15:22Z",
- "lastfm_fetched_at": "2019-08-24T14:15:22Z",
- "metadata_sources": {
- "property1": "string",
- "property2": "string"
}, - "last_scanned_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "reason": "string"
}
]| page | integer Default: 1 |
| page_size | integer Default: 50 |
| sort | string |
| order | string Enum: "asc" "desc" |
{- "artists": [
- {
- "id": "string",
- "name": "string",
- "sort_name": "string",
- "type": "string",
- "gender": "string",
- "origin": "string",
- "disambiguation": "string",
- "musicbrainz_id": "string",
- "audiodb_id": "string",
- "discogs_id": "string",
- "wikidata_id": "string",
- "deezer_id": "string",
- "spotify_id": "string",
- "genres": [
- "string"
], - "styles": [
- "string"
], - "moods": [
- "string"
], - "years_active": "string",
- "born": "string",
- "formed": "string",
- "died": "string",
- "disbanded": "string",
- "biography": "string",
- "path": "string",
- "library_id": "string",
- "nfo_exists": true,
- "thumb_exists": true,
- "fanart_exists": true,
- "fanart_count": 0,
- "logo_exists": true,
- "banner_exists": true,
- "thumb_low_res": true,
- "fanart_low_res": true,
- "logo_low_res": true,
- "banner_low_res": true,
- "thumb_placeholder": "string",
- "fanart_placeholder": "string",
- "logo_placeholder": "string",
- "banner_placeholder": "string",
- "health_score": 0.1,
- "is_excluded": true,
- "exclusion_reason": "string",
- "is_classical": true,
- "locked": true,
- "lock_source": "string",
- "locked_at": "2019-08-24T14:15:22Z",
- "locked_fields": [
- "string"
], - "audiodb_id_fetched_at": "2019-08-24T14:15:22Z",
- "discogs_id_fetched_at": "2019-08-24T14:15:22Z",
- "wikidata_id_fetched_at": "2019-08-24T14:15:22Z",
- "lastfm_fetched_at": "2019-08-24T14:15:22Z",
- "metadata_sources": {
- "property1": "string",
- "property2": "string"
}, - "last_scanned_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "total": 0,
- "page": 0,
- "page_size": 0
}Prevents automated operations (rule fixers, metadata fetchers, image operations) from modifying this artist. Manual edits remain allowed.
| id required | string |
{- "id": "string",
- "name": "string",
- "sort_name": "string",
- "type": "string",
- "gender": "string",
- "origin": "string",
- "disambiguation": "string",
- "musicbrainz_id": "string",
- "audiodb_id": "string",
- "discogs_id": "string",
- "wikidata_id": "string",
- "deezer_id": "string",
- "spotify_id": "string",
- "genres": [
- "string"
], - "styles": [
- "string"
], - "moods": [
- "string"
], - "years_active": "string",
- "born": "string",
- "formed": "string",
- "died": "string",
- "disbanded": "string",
- "biography": "string",
- "path": "string",
- "library_id": "string",
- "nfo_exists": true,
- "thumb_exists": true,
- "fanart_exists": true,
- "fanart_count": 0,
- "logo_exists": true,
- "banner_exists": true,
- "thumb_low_res": true,
- "fanart_low_res": true,
- "logo_low_res": true,
- "banner_low_res": true,
- "thumb_placeholder": "string",
- "fanart_placeholder": "string",
- "logo_placeholder": "string",
- "banner_placeholder": "string",
- "health_score": 0.1,
- "is_excluded": true,
- "exclusion_reason": "string",
- "is_classical": true,
- "locked": true,
- "lock_source": "string",
- "locked_at": "2019-08-24T14:15:22Z",
- "locked_fields": [
- "string"
], - "audiodb_id_fetched_at": "2019-08-24T14:15:22Z",
- "discogs_id_fetched_at": "2019-08-24T14:15:22Z",
- "wikidata_id_fetched_at": "2019-08-24T14:15:22Z",
- "lastfm_fetched_at": "2019-08-24T14:15:22Z",
- "metadata_sources": {
- "property1": "string",
- "property2": "string"
}, - "last_scanned_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Removes the metadata lock, allowing automated operations to modify this artist again.
| id required | string |
{- "id": "string",
- "name": "string",
- "sort_name": "string",
- "type": "string",
- "gender": "string",
- "origin": "string",
- "disambiguation": "string",
- "musicbrainz_id": "string",
- "audiodb_id": "string",
- "discogs_id": "string",
- "wikidata_id": "string",
- "deezer_id": "string",
- "spotify_id": "string",
- "genres": [
- "string"
], - "styles": [
- "string"
], - "moods": [
- "string"
], - "years_active": "string",
- "born": "string",
- "formed": "string",
- "died": "string",
- "disbanded": "string",
- "biography": "string",
- "path": "string",
- "library_id": "string",
- "nfo_exists": true,
- "thumb_exists": true,
- "fanart_exists": true,
- "fanart_count": 0,
- "logo_exists": true,
- "banner_exists": true,
- "thumb_low_res": true,
- "fanart_low_res": true,
- "logo_low_res": true,
- "banner_low_res": true,
- "thumb_placeholder": "string",
- "fanart_placeholder": "string",
- "logo_placeholder": "string",
- "banner_placeholder": "string",
- "health_score": 0.1,
- "is_excluded": true,
- "exclusion_reason": "string",
- "is_classical": true,
- "locked": true,
- "lock_source": "string",
- "locked_at": "2019-08-24T14:15:22Z",
- "locked_fields": [
- "string"
], - "audiodb_id_fetched_at": "2019-08-24T14:15:22Z",
- "discogs_id_fetched_at": "2019-08-24T14:15:22Z",
- "wikidata_id_fetched_at": "2019-08-24T14:15:22Z",
- "lastfm_fetched_at": "2019-08-24T14:15:22Z",
- "metadata_sources": {
- "property1": "string",
- "property2": "string"
}, - "last_scanned_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Adds the named field to the artist's locked-field set. This is persisted state that Stillwater mirrors to Emby's LockedFields via the connection sync path. Refresh-cycle enforcement (skipping locked fields on rewrite) is implemented per-rule at the refresh layer and is not guaranteed by this endpoint in isolation.
| id required | string |
| field required | string |
{- "id": "string",
- "name": "string",
- "sort_name": "string",
- "type": "string",
- "gender": "string",
- "origin": "string",
- "disambiguation": "string",
- "musicbrainz_id": "string",
- "audiodb_id": "string",
- "discogs_id": "string",
- "wikidata_id": "string",
- "deezer_id": "string",
- "spotify_id": "string",
- "genres": [
- "string"
], - "styles": [
- "string"
], - "moods": [
- "string"
], - "years_active": "string",
- "born": "string",
- "formed": "string",
- "died": "string",
- "disbanded": "string",
- "biography": "string",
- "path": "string",
- "library_id": "string",
- "nfo_exists": true,
- "thumb_exists": true,
- "fanart_exists": true,
- "fanart_count": 0,
- "logo_exists": true,
- "banner_exists": true,
- "thumb_low_res": true,
- "fanart_low_res": true,
- "logo_low_res": true,
- "banner_low_res": true,
- "thumb_placeholder": "string",
- "fanart_placeholder": "string",
- "logo_placeholder": "string",
- "banner_placeholder": "string",
- "health_score": 0.1,
- "is_excluded": true,
- "exclusion_reason": "string",
- "is_classical": true,
- "locked": true,
- "lock_source": "string",
- "locked_at": "2019-08-24T14:15:22Z",
- "locked_fields": [
- "string"
], - "audiodb_id_fetched_at": "2019-08-24T14:15:22Z",
- "discogs_id_fetched_at": "2019-08-24T14:15:22Z",
- "wikidata_id_fetched_at": "2019-08-24T14:15:22Z",
- "lastfm_fetched_at": "2019-08-24T14:15:22Z",
- "metadata_sources": {
- "property1": "string",
- "property2": "string"
}, - "last_scanned_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}| id required | string |
| field required | string |
{- "id": "string",
- "name": "string",
- "sort_name": "string",
- "type": "string",
- "gender": "string",
- "origin": "string",
- "disambiguation": "string",
- "musicbrainz_id": "string",
- "audiodb_id": "string",
- "discogs_id": "string",
- "wikidata_id": "string",
- "deezer_id": "string",
- "spotify_id": "string",
- "genres": [
- "string"
], - "styles": [
- "string"
], - "moods": [
- "string"
], - "years_active": "string",
- "born": "string",
- "formed": "string",
- "died": "string",
- "disbanded": "string",
- "biography": "string",
- "path": "string",
- "library_id": "string",
- "nfo_exists": true,
- "thumb_exists": true,
- "fanart_exists": true,
- "fanart_count": 0,
- "logo_exists": true,
- "banner_exists": true,
- "thumb_low_res": true,
- "fanart_low_res": true,
- "logo_low_res": true,
- "banner_low_res": true,
- "thumb_placeholder": "string",
- "fanart_placeholder": "string",
- "logo_placeholder": "string",
- "banner_placeholder": "string",
- "health_score": 0.1,
- "is_excluded": true,
- "exclusion_reason": "string",
- "is_classical": true,
- "locked": true,
- "lock_source": "string",
- "locked_at": "2019-08-24T14:15:22Z",
- "locked_fields": [
- "string"
], - "audiodb_id_fetched_at": "2019-08-24T14:15:22Z",
- "discogs_id_fetched_at": "2019-08-24T14:15:22Z",
- "wikidata_id_fetched_at": "2019-08-24T14:15:22Z",
- "lastfm_fetched_at": "2019-08-24T14:15:22Z",
- "metadata_sources": {
- "property1": "string",
- "property2": "string"
}, - "last_scanned_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Sets the locked flag on the matching artist_images row. This is persisted state. Whether a given refresh/rewrite path honours the flag is implemented per-publisher and is not guaranteed by this endpoint in isolation. The imageId must belong to the artist identified by the path id.
| id required | string |
| imageId required | string |
{- "images": [
- { }
]
}| id required | string |
{- "artist": {
- "id": "string",
- "name": "string",
- "sort_name": "string",
- "type": "string",
- "gender": "string",
- "origin": "string",
- "disambiguation": "string",
- "musicbrainz_id": "string",
- "audiodb_id": "string",
- "discogs_id": "string",
- "wikidata_id": "string",
- "deezer_id": "string",
- "spotify_id": "string",
- "genres": [
- "string"
], - "styles": [
- "string"
], - "moods": [
- "string"
], - "years_active": "string",
- "born": "string",
- "formed": "string",
- "died": "string",
- "disbanded": "string",
- "biography": "string",
- "path": "string",
- "library_id": "string",
- "nfo_exists": true,
- "thumb_exists": true,
- "fanart_exists": true,
- "fanart_count": 0,
- "logo_exists": true,
- "banner_exists": true,
- "thumb_low_res": true,
- "fanart_low_res": true,
- "logo_low_res": true,
- "banner_low_res": true,
- "thumb_placeholder": "string",
- "fanart_placeholder": "string",
- "logo_placeholder": "string",
- "banner_placeholder": "string",
- "health_score": 0.1,
- "is_excluded": true,
- "exclusion_reason": "string",
- "is_classical": true,
- "locked": true,
- "lock_source": "string",
- "locked_at": "2019-08-24T14:15:22Z",
- "locked_fields": [
- "string"
], - "audiodb_id_fetched_at": "2019-08-24T14:15:22Z",
- "discogs_id_fetched_at": "2019-08-24T14:15:22Z",
- "wikidata_id_fetched_at": "2019-08-24T14:15:22Z",
- "lastfm_fetched_at": "2019-08-24T14:15:22Z",
- "metadata_sources": {
- "property1": "string",
- "property2": "string"
}, - "last_scanned_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}, - "members": [
- {
- "id": "string",
- "artist_id": "string",
- "member_name": "string",
- "member_mbid": "string",
- "instruments": [
- "string"
], - "vocal_type": "string",
- "date_joined": "string",
- "date_left": "string",
- "is_original_member": true,
- "sort_order": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
]
}| id required | string |
{- "artist_id": "string",
- "artist_name": "string",
- "violations": [
- { }
], - "rules_passed": 0,
- "rules_total": 0,
- "health_score": 0,
- "warning": "string"
}Updates one metadata field on the artist. Supported fields: biography, genres, styles, moods, formed, born, disbanded, died, years_active, type, gender, origin, name, sort_name, disambiguation, musicbrainz_id, audiodb_id, discogs_id, wikidata_id, deezer_id.
Validation rules: name must be non-empty (rejects empty or whitespace-only strings); musicbrainz_id must be a valid UUID when non-empty (an empty string clears the stored ID).
| id required | string |
| field required | string |
| value required | string |
{- "value": "string"
}{- "status": "string",
- "field": "string",
- "value": "string"
}| id required | string |
| field required | string |
{- "field": "string",
- "results": [
- {
- "provider": "string",
- "value": "string",
- "values": [
- "string"
], - "has_data": true,
- "error": "string"
}
]
}| id required | string |
| name | string |
| mbid | string |
| instruments | Array of strings |
| vocal_type | string |
| date_joined | string |
| date_left | string |
| is_active | boolean |
[- {
- "name": "string",
- "mbid": "string",
- "instruments": [
- "string"
], - "vocal_type": "string",
- "date_joined": "string",
- "date_left": "string",
- "is_active": true
}
]{- "status": "string"
}Renames the artist's directory to the supplied name and updates the stored path. Decoupled from the Name field edit (PATCH /artists/{id}/fields/name) so that editing display name does not have a filesystem side-effect that can break Emby/Jellyfin item-to-path mappings. See issue #1077.
| id required | string |
| new_dirname required | string New leaf directory name. Must be a single path segment with no separators ("/" or ""); "." and ".." are also rejected. |
{- "new_dirname": "string"
}{- "status": "renamed",
- "new_path": "string"
}Triggers a full metadata refresh. If the artist has no MusicBrainz ID, returns a disambiguation prompt instead.
| id required | string |
{- "status": "string",
- "sources": [
- {
- "field": "string",
- "provider": "string"
}
], - "warning": "string"
}| id required | string |
| mbid | string |
| discogs_id | string |
| source | string |
{- "mbid": "string",
- "discogs_id": "string",
- "source": "string"
}{- "status": "string",
- "sources": [
- {
- "field": "string",
- "provider": "string"
}
], - "warning": "string"
}Returns computed diffs between the current Stillwater metadata and the last-known MusicBrainz values captured during the most recent provider refresh. Only fields where the Stillwater value is non-empty and differs from the MusicBrainz snapshot are included.
| id required | string |
{- "artist_id": "string",
- "musicbrainz_id": "string",
- "diffs": [
- {
- "field": "string",
- "stillwater_value": "string",
- "musicbrainz_value": "string",
- "source": "string"
}
], - "contribution_mode": "disabled",
- "last_fetched_at": "2019-08-24T14:15:22Z"
}Returns the disambiguation form so the user can link (or re-link) a MusicBrainz entry. When clear_ids is true, all provider IDs are wiped first (destructive "Re-identify" flow). Without it, existing IDs are preserved (non-destructive "Identify" flow).
| id required | string |
| clear_ids | string Value: "true" When set to "true", clears all provider IDs and fetch timestamps before returning the disambiguation form. Omit for the non-destructive identify flow. |
{- "status": "string",
- "artist": "string",
- "message": "string"
}| id required | string |
{- "artist_id": "string",
- "artist_name": "string",
- "violations": [
- { }
], - "rules_passed": 0,
- "rules_total": 0,
- "health_score": 0,
- "warning": "string"
}Returns all rules with their filesystem_dependent flag set. Also includes has_local_library indicating whether at least one library with a filesystem path exists. Filesystem-dependent rules cannot be enabled when has_local_library is false.
{- "rules": [
- { }
], - "has_local_library": true
}| id required | string |
| enabled | boolean |
| automation_mode | string Enum: "auto" "manual" Automation mode for this rule. Use the enabled flag to disable a rule; "disabled" is not a valid automation_mode value. |
| config | object |
{- "enabled": true,
- "automation_mode": "auto",
- "config": { }
}{ }Launches rule evaluation in a background goroutine and returns immediately. Shares the status slot with run-all; poll GET /rules/run-all/status for progress. Defaults to incremental scope (only artists flagged dirty since their last evaluation are processed). Pass scope=all to force evaluation against every eligible artist.
| id required | string |
| scope | string Default: "incremental" Enum: "incremental" "all" Which artists to evaluate. Defaults to "incremental" (only artists flagged dirty since their last evaluation). "all" is the admin "Re-evaluate All" path that forces a full sweep. |
{- "running": true,
- "status": "idle",
- "scope": "incremental",
- "rule_id": "string",
- "artists_processed": 0,
- "artists_total": 0,
- "artists_skipped": 0,
- "violations_found": 0,
- "violations_auto_fixed": 0,
- "violations_remaining": 0,
- "fixes_attempted": 0,
- "fixes_succeeded": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "completed_at": "2019-08-24T14:15:22Z",
- "error": "string"
}Starts an asynchronous run of all enabled rules. Returns 202 immediately; poll GET /rules/run-all/status for completion status. Defaults to incremental scope (only artists flagged dirty since their last evaluation are processed). Pass scope=all for the "Re-evaluate All" admin sweep that re-evaluates every eligible artist.
| scope | string Default: "incremental" Enum: "incremental" "all" Which artists to evaluate. Defaults to "incremental" (only artists flagged dirty since their last evaluation). "all" forces a full sweep. |
{- "running": true,
- "status": "idle",
- "scope": "incremental",
- "rule_id": "string",
- "artists_processed": 0,
- "artists_total": 0,
- "artists_skipped": 0,
- "violations_found": 0,
- "violations_auto_fixed": 0,
- "violations_remaining": 0,
- "fixes_attempted": 0,
- "fixes_succeeded": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "completed_at": "2019-08-24T14:15:22Z",
- "error": "string"
}Returns the current status of the most recent rule evaluation, whether triggered by run-all or a single-rule run. Both share the same status slot; only one can run at a time.
{- "running": true,
- "status": "idle",
- "scope": "incremental",
- "rule_id": "string",
- "artists_processed": 0,
- "artists_total": 0,
- "artists_skipped": 0,
- "violations_found": 0,
- "violations_auto_fixed": 0,
- "violations_remaining": 0,
- "fixes_attempted": 0,
- "fixes_succeeded": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "completed_at": "2019-08-24T14:15:22Z",
- "error": "string"
}Returns the current state of the rule evaluation scheduler.
{- "last_evaluation_at": "2019-08-24T14:15:22Z",
- "interval_minutes": 0,
- "next_evaluation_at": "2019-08-24T14:15:22Z",
- "scheduler_enabled": true
}| mode required | string Enum: "skip" "composer" "performer" |
{- "mode": "skip"
}{- "mode": "string"
}Returns paginated metadata change records for an artist, ordered by most recent first. Records are written by integration hooks in Phase 2; the table exists from Phase 1 onwards.
| id required | string Artist ID. |
| limit | integer [ 10 .. 500 ] Maximum number of records to return (10-500). Defaults to the caller's page_size preference when omitted. |
| offset | integer >= 0 Default: 0 Number of records to skip for pagination. |
{- "changes": [
- {
- "id": "string",
- "artist_id": "string",
- "field": "string",
- "old_value": "string",
- "new_value": "string",
- "source": "string",
- "created_at": "2019-08-24T14:15:22Z"
}
], - "total": 0,
- "limit": 0,
- "offset": 0
}Restores the old value from a metadata_changes record back to the artist field. Creates a new history entry with source "revert".
| id required | string Metadata change record ID. |
{- "reverted": true,
- "change_id": "string"
}Returns paginated metadata change records across all artists, ordered by most recent first.
| limit | integer [ 10 .. 500 ] Maximum number of records to return (10-500). Defaults to the caller's page_size preference when omitted. |
| offset | integer >= 0 Default: 0 Number of records to skip for pagination. |
| artist_id | string Filter to a single artist. |
| field | Array of strings Filter by field name. Repeat the parameter to include multiple fields (e.g. ?field=biography&field=genres). |
| source | Array of strings Filter by change source. Repeat the parameter to include multiple sources (e.g. ?source=manual&source=scan). Use wildcard suffix for prefix matching (e.g. ?source=provider:* matches all provider sources). |
string or string Include changes on or after this timestamp. Accepts RFC 3339 date-time (e.g. 2024-01-15T00:00:00Z) or a plain date (YYYY-MM-DD, interpreted as midnight UTC on that day). | |
string or string Include changes on or before this timestamp. Accepts RFC 3339 date-time (e.g. 2024-01-15T23:59:59Z) or a plain date (YYYY-MM-DD). A date-only value is treated as end-of-UTC-day (23:59:59.999999999) so the full day is included. |
{- "changes": [
- {
- "id": "string",
- "artist_id": "string",
- "field": "string",
- "old_value": "string",
- "new_value": "string",
- "source": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "artist_name": "string"
}
], - "total": 0,
- "limit": 0,
- "offset": 0
}| id required | string |
| alias required | string |
| source | string |
{- "alias": "string",
- "source": "string"
}{ }| id required | string |
| skip_crop | string Value: "true" When set to "true", skip geometry checking and save the image even if the aspect ratio does not match the slot requirement. |
| file required | string <binary> |
| type required | string Enum: "thumb" "fanart" "logo" "banner" |
{- "status": "string",
- "saved": [
- "string"
], - "type": "string",
- "sync_warnings": [
- "string"
], - "count": 0,
- "needs_crop": true,
- "required_ratio": 0,
- "actual_ratio": 0,
- "width": 0,
- "height": 0,
- "image_data": "string"
}| id required | string |
| skip_crop | string Value: "true" When set to "true", skip geometry checking and save the image even if the aspect ratio does not match the slot requirement. |
| url required | string <uri> |
| type required | string Enum: "thumb" "fanart" "logo" "banner" |
{- "type": "thumb"
}{- "status": "string",
- "saved": [
- "string"
], - "type": "string",
- "sync_warnings": [
- "string"
], - "count": 0,
- "needs_crop": true,
- "required_ratio": 0,
- "actual_ratio": 0,
- "width": 0,
- "height": 0,
- "image_data": "string"
}| id required | string |
| type | string Enum: "thumb" "fanart" "logo" "banner" |
| sort | string Default: "likes" Enum: "likes" "resolution" Sort order for results. Defaults to likes descending. |
{- "images": [
- { }
], - "errors": [
- "string"
]
}| id required | string |
| image_data required | string Base64-encoded image data |
| type required | string Enum: "thumb" "fanart" "logo" "banner" |
| x | integer |
| y | integer |
| width | integer |
| height | integer |
{- "image_data": "string",
- "type": "thumb",
- "x": 0,
- "y": 0,
- "width": 0,
- "height": 0
}{- "status": "string",
- "saved": [
- "string"
], - "type": "string",
- "sync_warnings": [
- "string"
]
}| id required | string |
| type required | string Enum: "thumb" "fanart" "logo" "banner" |
| sort | string Default: "likes" Enum: "likes" "resolution" Sort order for results. Defaults to likes descending. |
{- "images": [
- { }
]
}| id required | string |
| management | boolean When true and request is HTMX, returns the management gallery variant with per-slot controls |
[- {
- "index": 0,
- "filename": "string",
- "width": 0,
- "height": 0,
- "size": 0
}
]| id required | string |
| indices required | Array of integers |
{- "indices": [
- 0
]
}{- "status": "string",
- "deleted": [
- "string"
], - "count": 0,
- "sync_warnings": [
- "string"
]
}| id required | string |
| urls required | Array of strings <uri> <= 20 items [ items <uri > ] |
{
}{- "status": "string",
- "saved": [
- "string"
], - "errors": [
- "string"
], - "count": 0,
- "sync_warnings": [
- "string"
]
}| id required | string |
| order required | Array of integers Permutation of current slot indices defining the new order. order[i] = current slot that becomes new slot i. |
{- "order": [
- 0
]
}{- "status": "string",
- "count": 0,
- "sync_warnings": [
- "string"
]
}| id required | string |
| slot required | integer |
| connection_id required | string ID of the platform connection to download from |
| platform_index required | integer >= 0 0-based index of the backdrop on the platform |
{- "connection_id": "string",
- "platform_index": 0
}{- "status": "string",
- "slot": 0,
- "count": 0,
- "sync_warnings": [
- "string"
]
}| id required | string |
{- "connections": [
- {
- "connection_id": "string",
- "connection_name": "string",
- "connection_type": "emby",
- "backdrops": [
- {
- "index": 0,
- "thumbnail_url": "string"
}
]
}
]
}| id required | string |
{- "slots": [
- {
- "index": 0,
- "state": "synced",
- "connections": [
- {
- "connection_id": "string",
- "name": "string",
- "type": "emby",
- "synced": true
}
]
}
], - "state": "string"
}Picks a random artist that has at least one fanart/backdrop image and returns a 307 redirect to that artist's fanart file endpoint. Used by the ambient backdrop feature to display a blurred background image in the layout shell. Returns 404 if no artists have fanart images.
| id required | string |
| type required | string Enum: "thumb" "fanart" "logo" "banner" |
{- "type": "string",
- "filename": "string",
- "width": 0,
- "height": 0,
- "size": 0,
- "modified": "2019-08-24T14:15:22Z"
}For pathless artists, always returns no conflict since there is no on-disk NFO file to be modified externally.
| id required | string |
{- "has_conflict": true,
- "reason": "string",
- "external_writer": "string",
- "last_modified": "2019-08-24T14:15:22Z"
}| id required | string |
[- {
- "artist_id": "string",
- "connection_id": "string",
- "platform_artist_id": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
]| id required | string |
| connectionId required | string |
| platform_artist_id required | string |
{- "platform_artist_id": "string"
}{- "status": "string"
}Fetches the current state of an artist on a platform connection and returns an HTML partial comparing Stillwater and platform metadata. Used by HTMX for lazy-loading platform state cards on the artist detail page.
| id required | string |
| connection_id required | string |
| readonly | string Enum: "true" "false" When "true", returns a read-only card with additional platform-only fields (sort name, tags, backdrop count, locked status). Defaults to the interactive comparison card. |
{- "error": "string",
- "details": [
- "string"
]
}Fetches the artist's metadata from the specified platform connection and overwrites local biography, genres, and date fields with any non-empty values returned by the platform.
| id required | string |
| connection_id required | string |
{- "status": "string",
- "updated": [
- "string"
]
}| id required | string |
| connection_id required | string |
| platform_artist_id | string Platform-specific artist ID. If omitted, the server looks up a stored mapping from the artist_platform_ids table. Returns 400 if no stored mapping exists. |
{- "connection_id": "string",
- "platform_artist_id": "string"
}{- "status": "string"
}| id required | string |
| connection_id required | string |
| platform_artist_id | string Platform-specific artist ID. If omitted, the server looks up a stored mapping from the artist_platform_ids table. Returns 400 if no stored mapping exists. |
| image_types | Array of strings Items Enum: "thumb" "fanart" "logo" "banner" Types to push (defaults to all four) |
{- "connection_id": "string",
- "platform_artist_id": "string",
- "image_types": [
- "thumb"
]
}{- "uploaded": [
- "string"
], - "errors": [
- "string"
]
}| id required | string |
| type required | string Enum: "thumb" "fanart" "logo" "banner" |
| connection_id required | string |
| platform_artist_id | string Platform-specific artist ID. If omitted, the server looks up a stored mapping from the artist_platform_ids table. Returns 400 if no stored mapping exists. |
{- "connection_id": "string",
- "platform_artist_id": "string"
}{- "status": "deleted"
}| name required | string |
| nfo_enabled | boolean |
| nfo_format | string Enum: "kodi" "jellyfin" |
| image_naming | object |
{- "name": "string",
- "nfo_enabled": true,
- "nfo_format": "kodi",
- "image_naming": { }
}{ }| id required | string |
| name | string |
| nfo_enabled | boolean |
| nfo_format | string |
| image_naming | object |
{- "name": "string",
- "nfo_enabled": true,
- "nfo_format": "string",
- "image_naming": { }
}{ }[- {
- "id": "string",
- "name": "string",
- "type": "emby",
- "url": "string",
- "has_key": true,
- "has_platform_user_id": true,
- "enabled": true,
- "status": "string",
- "status_message": "string",
- "last_checked_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "feature_library_import": true,
- "feature_nfo_write": true,
- "feature_image_write": true,
- "feature_metadata_push": true,
- "feature_trigger_refresh": true,
- "library_count": 0,
- "artist_count": 0
}
]| name required | string |
| type required | string Enum: "emby" "jellyfin" "lidarr" |
| url required | string |
| api_key | string |
| enabled | boolean |
| skip_test | boolean |
{- "name": "string",
- "type": "emby",
- "url": "string",
- "api_key": "string",
- "enabled": true,
- "skip_test": true
}{- "id": "string",
- "name": "string",
- "type": "emby",
- "url": "string",
- "has_key": true,
- "has_platform_user_id": true,
- "enabled": true,
- "status": "string",
- "status_message": "string",
- "last_checked_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "feature_library_import": true,
- "feature_nfo_write": true,
- "feature_image_write": true,
- "feature_metadata_push": true,
- "feature_trigger_refresh": true,
- "library_count": 0,
- "artist_count": 0
}| id required | string |
{- "id": "string",
- "name": "string",
- "type": "emby",
- "url": "string",
- "has_key": true,
- "has_platform_user_id": true,
- "enabled": true,
- "status": "string",
- "status_message": "string",
- "last_checked_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "feature_library_import": true,
- "feature_nfo_write": true,
- "feature_image_write": true,
- "feature_metadata_push": true,
- "feature_trigger_refresh": true,
- "library_count": 0,
- "artist_count": 0
}| id required | string |
| name | string |
| type | string |
| url | string |
| api_key | string |
| enabled | boolean |
{- "name": "string",
- "type": "string",
- "url": "string",
- "api_key": "string",
- "enabled": true
}{- "id": "string",
- "name": "string",
- "type": "emby",
- "url": "string",
- "has_key": true,
- "has_platform_user_id": true,
- "enabled": true,
- "status": "string",
- "status_message": "string",
- "last_checked_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "feature_library_import": true,
- "feature_nfo_write": true,
- "feature_image_write": true,
- "feature_metadata_push": true,
- "feature_trigger_refresh": true,
- "library_count": 0,
- "artist_count": 0
}When no library has a filesystem path configured, returns an empty risk list immediately since there are no on-disk files to conflict with. Otherwise checks all enabled connections for NFO/image writing configuration that could overwrite Stillwater-managed files.
{- "has_risk": true,
- "risks": [
- {
- "connection_id": "string",
- "connection_name": "string",
- "connection_type": "string",
- "nfo_writer": true,
- "library_name": "string",
- "error": "string"
}
]
}| id required | string |
| feature_library_import | boolean |
| feature_nfo_write | boolean |
| feature_image_write | boolean |
| feature_metadata_push | boolean |
| feature_trigger_refresh | boolean |
{- "feature_library_import": true,
- "feature_nfo_write": true,
- "feature_image_write": true,
- "feature_metadata_push": true,
- "feature_trigger_refresh": true
}{- "status": "string"
}Returns the current fetcher, saver, and metadata downloader configuration for all music libraries on the connected platform. For Emby/Jellyfin this is per-library; for Lidarr it is global metadata consumers.
| id required | string |
{- "connection_type": "emby",
- "libraries": [
- {
- "library_id": "string",
- "library_name": "string",
- "image_fetchers": [
- "string"
], - "metadata_fetchers": [
- "string"
], - "metadata_savers": [
- "string"
], - "has_conflicts": true,
- "needs_lockdata": true,
- "enable_internet_providers": true
}
], - "note": "string"
}Disables conflicting fetchers, savers, and metadata downloaders on the connected platform. For Emby/Jellyfin this operates per-library (requires library_id). For Lidarr this disables a global metadata consumer (requires consumer_id).
| id required | string |
| library_id | string Required for Emby/Jellyfin connections. |
| consumer_id | integer Required for Lidarr connections. |
{- "library_id": "string",
- "consumer_id": 0
}{- "status": "string",
- "note": "string"
}When enabled=true, Stillwater PATCHes the peer's music library options to disable SaveLocalMetadata + MetadataSavers (or Lidarr's metadata consumers), snapshotting the prior config so it can be restored when the toggle is flipped off. When enabled=false, Stillwater replays the snapshot onto the peer and clears it.
| id required | string |
The HTMX toggle in the Connections settings page posts form-encoded; API/curl callers can use JSON. The handler accepts either and also honors an ?enabled= query param as a last resort.
| enabled required | boolean |
{- "enabled": true
}{- "connection_id": "string",
- "feature_manage_server_files": true
}Returns the aggregated conflict state across every configured connection. Used by the UI banner and by external monitoring to audit whether Stillwater's writes are currently gated. Pass refresh=1 to force a synchronous re-query of every peer (useful after a remediation).
| refresh | string Value: "1" |
{- "generated_at": "2019-08-24T14:15:22Z",
- "connections": [
- { }
], - "round_trips": [
- { }
], - "foreign_files": {
- "count": 0
}
}HTMX-loaded by the onboarding wizard when the user transitions into step 5 (conflict pre-flight, see #1184). Returns the same per-state body partials the persistent banner uses but with OOBE-specific copy and a hidden gate input. Empty-body (204) responses indicate the conflict detector is not configured. POST is used because the handler mutates state on every call (writes the conflict_check_completed_at marker; with refresh=1 also invalidates the detector cache). Routing as POST keeps the endpoint inside CSRF protection.
| refresh | string Value: "1" |
HTMX consumers swap the response into #conflict-banner. Empty-body responses (204) indicate detection is not available; an HTML fragment is returned otherwise.
| refresh | string Value: "1" |
Returns an aggregate summary of platform fetcher/saver management status for a connection. Used for connection card badges.
| id required | string |
{- "total_libraries": 0,
- "managed_libraries": 0,
- "total_consumers": 0,
- "has_conflicts": true,
- "needs_lockdata": true
}After creating each library, automatically starts a background populate operation to fetch artists from the connection. Poll GET /api/v1/libraries/{libId}/operation/status for progress.
| id required | string |
required | Array of objects |
{- "libraries": [
- {
- "external_id": "string",
- "name": "string"
}
]
}[- {
- "id": "string",
- "name": "string",
- "path": "string",
- "type": "regular",
- "source": "manual",
- "connection_id": "string",
- "external_id": "string",
- "fs_watch": 0,
- "fs_poll_interval": 0,
- "shared_fs_status": "",
- "shared_fs_evidence": "string",
- "shared_fs_peer_library_ids": "string",
- "nfo_lock_data": true,
- "fs_notify_supported": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
]| id required | string |
| libId required | string |
{- "library_id": "string",
- "library_name": "string",
- "operation": "populate",
- "status": "running",
- "message": "string",
- "started_at": "2019-08-24T14:15:22Z",
- "completed_at": "2019-08-24T14:15:22Z"
}| id required | string |
| libId required | string |
{- "library_id": "string",
- "library_name": "string",
- "operation": "populate",
- "status": "running",
- "message": "string",
- "started_at": "2019-08-24T14:15:22Z",
- "completed_at": "2019-08-24T14:15:22Z"
}| name required | string |
| api_key required | string |
| skip_test | boolean |
{- "api_key": "string",
- "skip_test": true
}{- "status": "string"
}| name required | string |
| base_url required | string Mirror base URL (must be http or https) |
| rate_limit | number Requests per second (default 10, max 100) |
{- "base_url": "string",
- "rate_limit": 0
}{- "status": "string",
- "test": "ok",
- "test_error": "string"
}required | Array of objects |
{- "priorities": [
- {
- "field": "string",
- "providers": [
- "string"
]
}
]
}{- "status": "string"
}Deletes every stored provider.priority.* settings row so the priority order and per-field disabled-provider lists fall back to the built-in defaults. For HTMX requests, returns the re-rendered priority chip rows fragment; otherwise returns JSON.
{- "status": "string",
- "priorities": [
- { }
]
}| mode | string Default: "prompt_no_match" Enum: "prompt_no_match" "yolo" "auto_exact" "auto_similar" |
{- "mode": "prompt_no_match"
}{ }Runs the requested action for each artist in the supplied ID list. Only one bulk action may run at a time; concurrent starts are rejected with 409 Conflict. The accepted actions are run_rules, re_identify_auto (legacy alias: re_identify), scan, and fetch_images. The review flow is started separately via POST /artists/re-identify/wizard. Progress is reported via GET /artists/bulk-actions/status and a bulk.completed SSE event is published when the run finishes.
| action required | string Enum: "run_rules" "re_identify" "re_identify_auto" "scan" "fetch_images" Action to apply to each artist in the list. The legacy value re_identify is an alias for re_identify_auto; the wizard variant (re_identify_review) is dispatched separately via POST /artists/re-identify/wizard. |
| ids required | Array of strings [ 1 .. 1000 ] items [ items^[A-Za-z0-9_-]{1,64}$ ] Artist IDs to process (max 1000 per request). |
{- "action": "run_rules",
- "ids": [
- "string"
]
}{- "status": "running",
- "action": "run_rules",
- "total": 0
}Signals the running bulk action goroutine to stop at its next iteration. The action's progress snapshot is finalized with status=canceled so clients can distinguish canceled runs from normal completion.
{- "status": "canceling"
}Processes unidentified artists (missing MusicBrainz ID) through a 3-tier pipeline: connection-based matching, album comparison, and name-only search. Returns 409 if a job is already running.
| library_id | string Optional library ID to scope the identification to a single library. |
{- "library_id": "string"
}{- "status": "completed",
- "message": "string",
- "total": 0
}{- "status": "idle",
- "total": 0,
- "processed": 0,
- "auto_linked": 0,
- "queued": 0,
- "unmatched": 0,
- "failed": 0,
- "current_name": "string",
- "review_queue": [
- {
- "artist_id": "string",
- "artist_name": "string",
- "artist_path": "string",
- "tier": "connection",
- "candidates": [
- {
- "provider_id": "string",
- "name": "string",
- "sort_name": "string",
- "type": "string",
- "disambiguation": "string",
- "origin": "string",
- "score": 0,
- "musicbrainz_id": "string",
- "source": "string",
- "album_comparison": {
- "matches": [
- {
- "local_name": "string",
- "remote_name": "string",
- "matched": true
}
], - "local_only": [
- "string"
], - "remote_only": [
- "string"
], - "match_count": 0,
- "local_count": 0,
- "remote_count": 0,
- "match_percent": 0
}, - "confidence": 0.1,
- "reason": "string"
}
]
}
]
}Links an artist to a MusicBrainz ID and attempts a full metadata refresh. If the artist exists in the bulk-identify review queue, it is removed.
| artist_id required | string |
| mbid required | string MusicBrainz artist ID to link. |
| discogs_id | string Optional Discogs artist ID. |
{- "artist_id": "string",
- "mbid": "string",
- "discogs_id": "string"
}{- "status": "linked",
- "artist_id": "string",
- "mbid": "string"
}Creates a new wizard session over the supplied artist IDs and returns a session_id the UI uses to walk through per-artist candidate review. Candidates are fetched on demand (with the next step pre-fetched in the background). Sessions are in-memory and TTL-bounded; they are not persisted across restarts.
| ids required | Array of strings [ 1 .. 1000 ] items [ items^[A-Za-z0-9_-]{1,64}$ ] |
{- "ids": [
- "string"
]
}{- "session_id": "string",
- "total": 0,
- "index": 0
}| sid required | string |
| idx required | integer |
| mbid required | string |
| discogs_id | string |
{- "mbid": "string",
- "discogs_id": "string"
}{- "score": 0,
- "total_artists": 0,
- "compliant_artists": 0,
- "missing_nfo": 0,
- "missing_thumb": 0,
- "missing_fanart": 0,
- "missing_mbid": 0,
- "top_violations": [
- {
- "rule_id": "string",
- "rule_name": "string",
- "count": 0,
- "severity": "string"
}
]
}| page | integer Default: 1 |
| page_size | integer Default: 50 |
| search | string |
| filter | string |
| status | string Enum: "compliant" "non_compliant" |
{- "rows": [
- {
- "artist": { },
- "health_score": 0.1,
- "violations": [
- { }
], - "rules_passed_count": 0,
- "rules_evaluated_count": 0
}
], - "total": 0,
- "page": 0,
- "page_size": 0
}| days | integer [ 1 .. 365 ] Default: 30 Number of past days to include. Values outside 1-365 default to 30. |
{- "trend": [
- {
- "date": "2019-08-24",
- "created": 0,
- "resolved": 0
}
]
}{- "libraries": [
- {
- "library_id": "string",
- "library_name": "string",
- "total_artists": 0,
- "compliant_artists": 0,
- "score": 0,
- "missing_nfo": 0,
- "missing_thumb": 0,
- "missing_fanart": 0,
- "missing_mbid": 0
}
], - "overall": { }
}Returns field-level coverage percentages across all non-excluded artists. Artist type-aware fields (formed for groups, born for persons) are only counted for applicable artists. When library_id is provided the report is scoped to that library.
| library_id | string Scope the report to a specific library. Omit to include all libraries. |
{- "overall_score": 0.1,
- "total_artists": 0,
- "field_coverage": [
- {
- "field": "string",
- "count": 0,
- "total": 0,
- "percentage": 0.1
}
], - "library_coverage": [
- {
- "library_id": "string",
- "library_name": "string",
- "total_artists": 0,
- "score": 0.1,
- "fields": [
- {
- "field": "string",
- "count": 0,
- "total": 0,
- "percentage": 0.1
}
]
}
], - "lowest_completeness": [
- {
- "id": "string",
- "name": "string",
- "library_id": "string",
- "health_score": 0.1
}
]
}| name required | string |
| url required | string |
| type | string |
| events | Array of strings |
| enabled | boolean |
{- "name": "string",
- "url": "string",
- "type": "string",
- "events": [
- "string"
], - "enabled": true
}{- "id": "string",
- "name": "string",
- "url": "string",
- "type": "string",
- "events": [
- "string"
], - "enabled": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}{- "id": "string",
- "name": "string",
- "url": "string",
- "type": "string",
- "events": [
- "string"
], - "enabled": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}| id required | string |
| name | string |
| url | string |
| type | string |
| events | Array of strings |
| enabled | boolean |
{- "name": "string",
- "url": "string",
- "type": "string",
- "events": [
- "string"
], - "enabled": true
}{- "id": "string",
- "name": "string",
- "url": "string",
- "type": "string",
- "events": [
- "string"
], - "enabled": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Accepts inbound webhook events from Lidarr. Responds immediately with 200 and processes the event asynchronously. Uses standard authentication (session cookie, bearer token, or API key query parameter).
| eventType required | string Lidarr event type (Test, ArtistAdded, Grab, Download, AlbumImport) |
object |
{- "eventType": "string",
- "artist": {
- "id": 0,
- "name": "string",
- "path": "string",
- "mbId": "string",
- "foreignArtistId": "string"
}
}{- "status": "string"
}Accepts inbound webhook events from Emby. Responds immediately with 200 and processes the event asynchronously. Uses standard authentication (session cookie, bearer token, or API key query parameter).
| Event required | string Emby event type (system.notificationtest, library.new, item.updated, library.changed) |
object |
{- "Event": "string",
- "Item": {
- "Id": "string",
- "Name": "string",
- "Type": "string",
- "ProviderIds": {
- "property1": "string",
- "property2": "string"
}, - "Path": "string",
- "ArtistItems": [
- {
- "Id": "string",
- "Name": "string"
}
]
}
}{- "status": "string"
}Accepts inbound webhook events from the Jellyfin webhook plugin. Responds immediately with 200 and processes the event asynchronously. Uses standard authentication (session cookie, bearer token, or API key query parameter).
| NotificationType required | string Jellyfin event type (Test, ItemAdded, ItemUpdated, LibraryChanged) |
| ItemId | string |
| ItemType | string Media type, e.g. MusicAlbum |
| Name | string |
| Artist | string Artist name from the Jellyfin webhook plugin |
| Provider_musicbrainzalbumartist | string MusicBrainz album artist ID from the Jellyfin webhook plugin |
{- "NotificationType": "string",
- "ItemId": "string",
- "ItemType": "string",
- "Name": "string",
- "Artist": "string",
- "Provider_musicbrainzalbumartist": "string"
}{- "status": "string"
}Returns the direct subdirectories of the given absolute path. Only directories are returned; files are excluded. Hidden directories (those whose names start with a dot) are also excluded. Requires administrator role.
| path required | string Absolute filesystem path to list. Must be an absolute path; traversal segments (e.g. "..") are normalized by the server via filepath.Clean before listing. |
{- "path": "string",
- "entries": [
- "string"
], - "parent": "string"
}Update one or more settings as key-value string pairs. Known keys include: auto_fetch_images (true/false), show_platform_debug (true/false), notif_badge_enabled (true/false), notif_badge_severity_error (true/false), notif_badge_severity_warning (true/false), notif_badge_severity_info (true/false), rule_schedule.interval_minutes (integer as string; 0 to disable, or >= 5), backup_retention_count (positive integer as string), backup_max_age_days (integer as string), cache.image.max_size_mb (integer as string), provider.name_similarity_threshold (0-100 integer as string; minimum similarity score for provider name matching, set to 0 to disable validation, default 60), images.backdrop.target_count (1-10 integer as string; target number of backdrop/fanart images per artist, default 1).
| property name* additional property | string |
{- "property1": "string",
- "property2": "string"
}{- "status": "string"
}| level | string Enum: "trace" "debug" "info" "warn" "error" Minimum log severity to emit. |
| format | string Enum: "text" "json" Log output format. |
| file_path | string Path to the log file on disk. |
| file_max_size_mb | integer Maximum log file size in megabytes before rotation. |
| file_max_files | integer Maximum number of rotated log files to retain. |
| file_max_age_days | integer Maximum age in days before a rotated log file is deleted. |
{- "level": "trace",
- "format": "text",
- "file_path": "string",
- "file_max_size_mb": 0,
- "file_max_files": 0,
- "file_max_age_days": 0
}{- "level": "trace",
- "format": "text",
- "file_path": "string",
- "file_max_size_mb": 0,
- "file_max_files": 0,
- "file_max_age_days": 0
}| enabled required | boolean |
| interval_hours | integer |
{- "enabled": true,
- "interval_hours": 0
}{- "status": "string"
}| passphrase required | string |
{- "passphrase": "string"
}{- "version": "string",
- "app_version": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "salt": "string",
- "data": "string",
- "summary": {
- "settings": 0,
- "connections": 0,
- "platform_profiles": 0,
- "webhooks": 0,
- "provider_keys": 0,
- "priorities": 0,
- "rules": 0,
- "scraper_configs": 0,
- "user_preferences": 0,
- "libraries": 0,
- "libraries_skipped": 0,
- "api_tokens": 0,
- "api_tokens_skipped": 0,
- "users_imported": 0,
- "ownership_reassigned": 0
}
}| passphrase required | string |
required | object (PortableSettingsEnvelopeImport) Import-side wrapper for a previously exported settings file. The shape
is intentionally permissive: the importer accepts envelope versions
1.0-1.3, and earlier versions do not carry the per-section counters
that current writers emit. Required fields are limited to the core
crypto envelope (version + app_version + created_at + salt + data);
the optional |
| admin_fallback_tokens | boolean Opt-in. When true, API tokens whose original owner is absent on the target after the envelope's Users block has been applied are reassigned to the importing admin instead of skipped. The reassignment count surfaces in the response under ownership_reassigned. |
{- "passphrase": "string",
- "envelope": {
- "version": "string",
- "app_version": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "salt": "string",
- "data": "string",
- "summary": {
- "settings": 0,
- "connections": 0,
- "platform_profiles": 0,
- "webhooks": 0,
- "provider_keys": 0,
- "priorities": 0,
- "rules": 0,
- "scraper_configs": 0,
- "user_preferences": 0,
- "libraries": 0,
- "libraries_skipped": 0,
- "api_tokens": 0,
- "api_tokens_skipped": 0,
- "users_imported": 0,
- "ownership_reassigned": 0
}
}, - "admin_fallback_tokens": true
}{- "settings": 0,
- "connections": 0,
- "platform_profiles": 0,
- "webhooks": 0,
- "provider_keys": 0,
- "priorities": 0,
- "rules": 0,
- "scraper_configs": 0,
- "user_preferences": 0,
- "libraries": 0,
- "libraries_skipped": 0,
- "api_tokens": 0,
- "api_tokens_skipped": 0,
- "users_imported": 0,
- "ownership_reassigned": 0
}Returns the current NFO field mapping configuration that controls how genre, style, and mood data is written to NFO XML elements for Emby, Jellyfin, and Kodi compatibility.
{- "default_behavior": true,
- "moods_as_styles": true,
- "genre_sources": [
- "genres"
], - "advanced_remap": {
- "genre": [
- "genres"
], - "style": [
- "genres"
], - "mood": [
- "genres"
]
}
}Updates the NFO field mapping configuration. Controls how genre, style, and mood source categories map to NFO XML elements.
| default_behavior | boolean When true, writes each category to its native element (Kodi-compatible passthrough). Ignores moods_as_styles and genre_sources, but advanced_remap still takes higher precedence when non-null. |
| moods_as_styles | boolean When true, mood values are additionally written as style elements for Emby/Jellyfin Tag visibility. |
| genre_sources | Array of strings Items Enum: "genres" "styles" "moods" Which source categories feed the genre XML element. |
object or null Full source-to-element mapping matrix. Overrides all other settings when non-null. |
{- "default_behavior": true,
- "moods_as_styles": true,
- "genre_sources": [
- "genres"
], - "advanced_remap": {
- "genre": [
- "genres"
], - "style": [
- "genres"
], - "mood": [
- "genres"
]
}
}{- "default_behavior": true,
- "moods_as_styles": true,
- "genre_sources": [
- "genres"
], - "advanced_remap": {
- "genre": [
- "genres"
], - "style": [
- "genres"
], - "mood": [
- "genres"
]
}
}Returns all preference keys for the authenticated user, merged with defaults. Preferences are per-user appearance settings (theme, sidebar state, typography, etc.).
{- "theme": "dark",
- "sidebar_state": "full",
- "content_width": "narrow",
- "font_family": "system",
- "font_size": "small",
- "letter_spacing": "normal",
- "thumbnail_size": "small",
- "reduced_motion": "system",
- "lite_mode": "off",
- "language": "en",
- "notification_enabled": "true",
- "page_size": "50",
- "metadata_languages": "[\"en\"]",
- "auto_fetch_images": "true",
- "bg_opacity": "65"
}Returns the value of a single preference key for the authenticated user. For known fixed keys the value is merged with the compiled default when no row exists. For suppress_confirm_* keys a missing row returns "false".
required | string or string A known preference key (theme, sidebar_state, etc.) or a suppress_confirm_* key of the form suppress_confirm_{action} where {action} contains only lowercase letters, digits, and underscores. |
{- "key": "string",
- "value": "string"
}required | string or string A known preference key or a suppress_confirm_* key of the form suppress_confirm_{action}. suppress_confirm_* keys accept only "true" or "false". |
| value required | string |
{- "value": "string"
}{- "key": "string",
- "value": "string"
}[- {
- "id": "string",
- "name": "string",
- "path": "string",
- "type": "regular",
- "source": "manual",
- "connection_id": "string",
- "external_id": "string",
- "fs_watch": 0,
- "fs_poll_interval": 0,
- "shared_fs_status": "",
- "shared_fs_evidence": "string",
- "shared_fs_peer_library_ids": "string",
- "nfo_lock_data": true,
- "fs_notify_supported": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
]| name required | string |
| path | string |
| type | string Default: "regular" Enum: "regular" "classical" |
{- "name": "string",
- "path": "string",
- "type": "regular"
}{- "id": "string",
- "name": "string",
- "path": "string",
- "type": "regular",
- "source": "manual",
- "connection_id": "string",
- "external_id": "string",
- "fs_watch": 0,
- "fs_poll_interval": 0,
- "shared_fs_status": "",
- "shared_fs_evidence": "string",
- "shared_fs_peer_library_ids": "string",
- "nfo_lock_data": true,
- "fs_notify_supported": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}{- "library": {
- "id": "string",
- "name": "string",
- "path": "string",
- "type": "regular",
- "source": "manual",
- "connection_id": "string",
- "external_id": "string",
- "fs_watch": 0,
- "fs_poll_interval": 0,
- "shared_fs_status": "",
- "shared_fs_evidence": "string",
- "shared_fs_peer_library_ids": "string",
- "nfo_lock_data": true,
- "fs_notify_supported": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}, - "artist_count": 0
}| id required | string |
| name | string |
| path | string |
| type | string Enum: "regular" "classical" |
| fs_watch | integer |
| fs_poll_interval | integer Seconds between polls (60, 300, 900, or 1800) |
| nfo_lock_data | boolean When true, NFOs written for artists in this library carry |
{- "name": "string",
- "path": "string",
- "type": "regular",
- "fs_watch": 0,
- "fs_poll_interval": 0,
- "nfo_lock_data": true
}{- "id": "string",
- "name": "string",
- "path": "string",
- "type": "regular",
- "source": "manual",
- "connection_id": "string",
- "external_id": "string",
- "fs_watch": 0,
- "fs_poll_interval": 0,
- "shared_fs_status": "",
- "shared_fs_evidence": "string",
- "shared_fs_peer_library_ids": "string",
- "nfo_lock_data": true,
- "fs_notify_supported": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}| libId required | string |
{- "library_id": "string",
- "library_name": "string",
- "operation": "populate",
- "status": "running",
- "message": "string",
- "started_at": "2019-08-24T14:15:22Z",
- "completed_at": "2019-08-24T14:15:22Z"
}| id required | string |
{ }{- "status": "string"
}{- "providers": [
- {
- "provider": "string",
- "display_name": "string",
- "requires_auth": true,
- "has_key": true,
- "metadata_fields": [
- "string"
], - "image_fields": [
- "string"
]
}
]
}| status | string Default: "active" Enum: "active" "open" "dismissed" "resolved" "pending_choice" Filter by violation status. "active" returns open + pending_choice. |
| sort | string Default: "severity" Enum: "severity" "artist_name" "rule_id" "created_at" Sort column |
| order | string Default: "desc" Enum: "asc" "desc" Sort direction |
| severity | string Enum: "error" "warning" "info" Filter by severity level |
| category | string Enum: "nfo" "image" "metadata" Filter by rule category |
| rule_id | string Filter by specific rule ID |
{- "violations": [
- {
- "id": "string",
- "rule_id": "string",
- "artist_id": "string",
- "artist_name": "string",
- "library_name": "string",
- "severity": "error",
- "message": "string",
- "fixable": true,
- "status": "open",
- "candidates": [
- {
- "url": "string",
- "width": 0,
- "height": 0,
- "source": "string",
- "image_type": "string"
}
], - "dismissed_at": "2019-08-24T14:15:22Z",
- "resolved_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "count": 0
}| id required | string |
| url required | string |
| image_type required | string Enum: "thumb" "fanart" "logo" "banner" |
{- "url": "string",
- "image_type": "thumb"
}{- "status": "string"
}| ids | Array of strings Violation IDs to dismiss (omit to dismiss all) |
{- "ids": [
- "string"
]
}{- "dismissed": 0
}Applies the recommended fix for a single violation. For open fixable violations, routes through the appropriate fixer (NFO generation, metadata fetch, image fetch, directory rename, etc.). Returns the fix result with status and message. When the fix modifies NFO files, image files, or renames an artist directory, the response may include an undo_id that can be passed to POST /fix-undo/{undoId} within 30 seconds to revert it. Metadata-only fixes, dismissed violations, and failed fixes do not produce an undo_id.
| id required | string |
{- "status": "fixed",
- "message": "string",
- "undo_id": "string",
- "undo_expires_in": 0
}Reverts a fix that was applied within the undo window (30 seconds). The undoId is returned by POST /notifications/{id}/fix when the fix modified filesystem files. The undo entry is single-use and expires after 30 seconds. Returns 410 Gone when the window has elapsed or the token has already been used. On success, attempts to reopen the violation so it reappears as fixable in the notifications list; reopening may fail even when the revert itself succeeds, in which case the response status is still "reverted" with a warning message.
| undoId required | string Opaque undo token returned by the fix endpoint |
{- "status": "reverted",
- "message": "string"
}Starts an asynchronous operation that applies the recommended fix for all open fixable violations (pending_choice violations that require user candidate selection are excluded). Returns 202 with the total count. Poll /notifications/fix-all/status for progress.
| ids | Array of strings Optional subset of violation IDs. Omit to fix all fixable. |
{- "ids": [
- "string"
]
}{- "status": "completed",
- "message": "string",
- "total": 0
}Streams the current filtered violation list as a CSV file download. Respects the same filter and sort parameters as the list endpoint. Use format=json query param or Accept: application/json header for JSON output.
| status | string Default: "active" Enum: "active" "open" "dismissed" "resolved" "pending_choice" Filter by violation status. "active" returns open + pending_choice. |
| severity | string Enum: "error" "warning" "info" |
| category | string Enum: "nfo" "image" "metadata" |
| rule_id | string |
| sort | string Default: "severity" Enum: "severity" "artist_name" "rule_id" "created_at" |
| order | string Default: "desc" Enum: "asc" "desc" |
| format | string Value: "json" Set to "json" for JSON output instead of CSV. |
Returns the log files available for browsing. The current (actively written) file is listed first, followed by rotated backups newest-first. Returns an empty array when file logging is not configured.
[- {
- "name": "string",
- "size": 0,
- "mod_time": "2019-08-24T14:15:22Z",
- "is_current": true
}
]Deletes all rotated log files (all except the current actively-written file). Returns the number of files deleted and bytes freed. Does nothing if file logging is not configured.
{- "deleted": 0,
- "bytes_freed": 0
}Returns log entries from the in-memory ring buffer, or from a specific log file when the file parameter is provided. Supports filtering by level, search text, and component. Returns JSON for API clients or HTML fragments when the HX-Request header is set.
| file | string Filename of a log file to read (e.g. stillwater-2024-03-28T10-00-00.000.log). Must be a plain filename with no path separators. When set, reads from disk instead of the ring buffer; the after filter is ignored. Requires file logging to be configured. |
| level | string Enum: "trace" "debug" "info" "warn" "error" Minimum log level to include. Omit to return all levels. |
| search | string Case-insensitive substring search on log message |
| component | string Exact match on the component attribute |
| after | string <date-time> Only return entries after this RFC3339Nano timestamp (ring buffer only; ignored when file is set) |
| limit | integer [ 0 .. 500 ] Default: 100 Maximum number of entries to return (default 100, max 500) |
[- {
- "time": "2019-08-24T14:15:22Z",
- "level": "trace",
- "message": "string",
- "component": "string",
- "attrs": { }
}
]Queries the GitHub Releases API for the latest version on the configured channel (stable, prerelease, or nightly). Returns the current running version, the latest available version, and whether an update is available.
Uses POST rather than GET because the call mutates server-side updater
state (last_checked timestamp, cached latest / release_url /
update_available fields) and is therefore CSRF-protected.
In Docker environments, update_available may still be true but
POST /updates/apply will return 422.
{- "current": "v0.9.6",
- "latest": "v1.0.0",
- "channel": "stable",
- "update_available": true,
- "release_url": "string",
- "published_at": "2019-08-24T14:15:22Z"
}Returns the current state of the update lifecycle:
idle, checking, downloading, applying, or error.
Poll this endpoint after POST /updates/apply to track progress.
{- "state": "idle",
- "progress": 100,
- "error": "string",
- "last_checked": "2019-08-24T14:15:22Z",
- "is_docker": true,
- "update_available": true,
- "latest": "string",
- "release_url": "string",
- "restart_required": true,
- "pending_version": "string"
}Starts an asynchronous binary self-update. Downloads the latest release asset for the current platform, verifies its SHA256 checksum, and atomically replaces the running binary.
Returns 202 Accepted immediately. Poll GET /updates/status to track
progress.
Returns 422 when running inside a Docker container; re-pull the image instead. Returns 409 when an update is already in progress, or when a previous apply has staged a binary that requires a process restart before another apply can run.
Note: After a successful apply the process must be restarted manually
(or via the host process manager) to load the new binary. A future
release will add an automatic restart hook. Until then, a successful
apply is signalled by GET /updates/status returning
restart_required: true and pending_version set to the installed tag.
{ }{- "status": "string"
}Returns the current updater configuration: channel, enabled, auto_check, auto_update, and check_interval_hours. The auto_update field defaults to false and is ignored on Docker hosts (the scheduler no-ops on Docker; orchestration handles container image refresh).
{- "channel": "stable",
- "enabled": true,
- "auto_check": true,
- "auto_update": true,
- "check_interval_hours": 0,
- "last_auto_applied": "2019-08-24T14:15:22Z",
- "last_auto_applied_version": "string",
- "skipped_versions": [
- "string"
]
}Persists the updater configuration: channel (stable, prerelease, or
nightly), the top-level enabled switch, the auto_check toggle,
the auto_update toggle, and the check_interval_hours cadence used
by the background auto-check loop. Config is stored in the settings
key-value table.
auto_update defaults to false and is ignored on Docker hosts (the
scheduler no-ops on Docker; orchestration handles container image
refresh). When true alongside enabled and auto_check, the
scheduler calls Apply automatically after a successful check finds a
newer release that is not on the skip list.
Example PUT body:
{
"channel": "stable",
"enabled": true,
"auto_check": true,
"auto_update": false,
"check_interval_hours": 24
}
| channel required | string Enum: "stable" "prerelease" "nightly" Release channel to track. "stable" follows non-prerelease semver tags; "prerelease" also includes release candidates and beta builds; "nightly" follows date-stamped nightly builds (nightly-YYYYMMDD). |
| enabled | boolean Top-level kill switch for the updater. When false, the background auto-check loop is a no-op and manual Apply is rejected with a 403. Default: true. |
| auto_check required | boolean When true (and |
| auto_update | boolean When true (and |
| check_interval_hours | integer >= 0 How often the background auto-check loop polls GitHub, in hours. The persisted minimum is 1; a request value of 0 is coerced to the default (24) by the service layer (the handler only rejects negative values; the coercion runs inside Service.SetConfig so older clients that omit the field still validate). Negative values are rejected on PUT with a 400. |
{- "channel": "stable",
- "enabled": true,
- "auto_check": true,
- "auto_update": true,
- "check_interval_hours": 0
}{- "channel": "stable",
- "enabled": true,
- "auto_check": true,
- "auto_update": true,
- "check_interval_hours": 0,
- "last_auto_applied": "2019-08-24T14:15:22Z",
- "last_auto_applied_version": "string",
- "skipped_versions": [
- "string"
]
}Returns the persisted skip list. The scheduler reads this list on every tick and short-circuits AutoUpdate when the candidate tag is present.
{- "skipped_versions": [
- "string"
]
}Idempotent: a tag already present is a no-op. The scheduler honors the post-write list on the next tick.
| version required | string non-empty Release tag to skip (e.g. "v1.2.3" or "nightly-20260101"). |
{- "version": "string"
}{- "skipped_versions": [
- "string"
]
}Server-Sent Events endpoint that streams real-time system events to connected browser clients. Events include scan completions, rule violations, bulk operation results, and artist updates.
The connection sends a heartbeat comment every 30 seconds to keep the connection alive. Clients should auto-reconnect on disconnect.
Event format follows the SSE specification with named events:
event: scan.completed
data: {"type":"scan.completed","title":"Scan completed","message":"Library scan finished","timestamp":"...","data":{}}
{- "error": "string",
- "details": [
- "string"
]
}Returns every row in the foreign_files ledger, joined with the owning artist name. Foreign files are images matching media-server naming patterns ("backdrop*", "fanart*", "poster*", "logo*", "banner*", "thumb*", "clearart*", "disc*", "landscape*") that lack Stillwater EXIF provenance. Recorded by the periodic foreign-file scanner; never modified by this endpoint.
{- "count": 0,
- "items": [
- {
- "id": "string",
- "artist_id": "string",
- "artist_name": "string",
- "file_path": "string",
- "file_name": "string",
- "size_bytes": 0,
- "detected_at": "2019-08-24T14:15:22Z"
}
]
}Inserts a global-scope allowlist row for every distinct file_name currently in the foreign_files ledger and clears the ledger. Subsequent scans suppress those names across every artist. Returns the re-rendered conflict banner so HTMX can swap it in place.
{- "error": "string",
- "details": [
- "string"
]
}Adds an artist-scoped allowlist entry matching (entry.artist_id, entry.file_name) and removes the ledger row. The file remains on disk; future scans will not re-record it for that artist.
| id required | string |
{- "error": "string",
- "details": [
- "string"
]
}Atomically removes the file from disk via internal/filesystem (rename-to-tomb then unlink) and removes the matching ledger row. Does not create an allowlist entry; the file is gone, so re-detection is moot.
| id required | string |
{- "error": "string",
- "details": [
- "string"
]
}{- "count": 0,
- "items": [
- {
- "id": "string",
- "scope": "global",
- "artist_id": "string",
- "artist_name": "string",
- "file_name": "string",
- "note": "string",
- "created_at": "2019-08-24T14:15:22Z"
}
]
}Removes an allowlist row, re-enabling foreign-file detection for the matching file_name (artist-scoped or global, depending on the row).
| id required | string |
{- "error": "string",
- "details": [
- "string"
]
}