Stillwater API (0.25.0)

Download OpenAPI specification:

License: GPL-3.0

Artist metadata and image management for Emby, Jellyfin, and Kodi.

Health

Health check

Responses

Response samples

Content type
application/json
{
  • "status": "ok",
  • "version": "string",
  • "commit": "string",
  • "time": "2019-08-24T14:15:22Z"
}

Auth

Log in

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.

Request Body schema: application/json
required
username
required
string
password
required
string

Responses

Request samples

Content type
application/json
{
  • "username": "string",
  • "password": "string"
}

Response samples

Content type
application/json
{
  • "status": "string"
}

Initiate OIDC login

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.

Responses

Response samples

Content type
application/json
{
  • "error": "string",
  • "details": [
    ]
}

OIDC callback

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.

query Parameters
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

Responses

Create initial admin account

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.

Request Body schema: application/json
required
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

Responses

Request samples

Content type
application/json
{
  • "auth_method": "local",
  • "username": "string",
  • "password": "string",
  • "server_url": "http://example.com"
}

Response samples

Content type
application/json
{
  • "status": "string"
}

Log out

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "status": "string"
}

Get current user

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "user_id": "string"
}

Create API token

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
required
name
required
string
scopes
string
Default: "read"

Comma-separated scopes (default "read")

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "scopes": "read"
}

Response samples

Content type
application/json
{
  • "id": "string",
  • "token": "string",
  • "name": "string"
}

List API tokens

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Revoke API token

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "status": "string"
}

Permanently delete a token

Permanently removes a revoked token. Anonymizes associated audit log entries. Cannot delete active tokens.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "error": "string",
  • "details": [
    ]
}

Artists

List artists

Authorizations:
cookieAuthbearerAuthapiKeyAuth
query Parameters
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.

Responses

Response samples

Content type
application/json
{
  • "artists": [
    ],
  • "total": 0,
  • "page": 0,
  • "page_size": 0
}

Get artist count badge HTML fragment

Returns an HTML fragment containing the total artist count, used by the sidebar badge. Lightweight endpoint that fetches only the count.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Find duplicate artists

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
[
  • {
    }
]

List locked artists

Authorizations:
cookieAuthbearerAuthapiKeyAuth
query Parameters
page
integer
Default: 1
page_size
integer
Default: 50
sort
string
order
string
Enum: "asc" "desc"

Responses

Response samples

Content type
application/json
{
  • "artists": [
    ],
  • "total": 0,
  • "page": 0,
  • "page_size": 0
}

Lock an artist

Prevents automated operations (rule fixers, metadata fetchers, image operations) from modifying this artist. Manual edits remain allowed.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "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": [
    ],
  • "styles": [
    ],
  • "moods": [
    ],
  • "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": [
    ],
  • "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": {
    },
  • "last_scanned_at": "2019-08-24T14:15:22Z",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Unlock an artist

Removes the metadata lock, allowing automated operations to modify this artist again.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "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": [
    ],
  • "styles": [
    ],
  • "moods": [
    ],
  • "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": [
    ],
  • "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": {
    },
  • "last_scanned_at": "2019-08-24T14:15:22Z",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Lock a single metadata field

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
field
required
string

Responses

Response samples

Content type
application/json
{
  • "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": [
    ],
  • "styles": [
    ],
  • "moods": [
    ],
  • "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": [
    ],
  • "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": {
    },
  • "last_scanned_at": "2019-08-24T14:15:22Z",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Unlock a single metadata field

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
field
required
string

Responses

Response samples

Content type
application/json
{
  • "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": [
    ],
  • "styles": [
    ],
  • "moods": [
    ],
  • "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": [
    ],
  • "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": {
    },
  • "last_scanned_at": "2019-08-24T14:15:22Z",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Lock a single artist image slot

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
imageId
required
string

Responses

Response samples

Content type
application/json
{
  • "images": [
    ]
}

Unlock a single artist image slot

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
imageId
required
string

Responses

Response samples

Content type
application/json
{
  • "images": [
    ]
}

Get artist details

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "artist": {
    },
  • "members": [
    ]
}

Evaluate artist health

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "artist_id": "string",
  • "artist_name": "string",
  • "violations": [
    ],
  • "rules_passed": 0,
  • "rules_total": 0,
  • "health_score": 0,
  • "warning": "string"
}

Get field display view

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
field
required
string

Responses

Response samples

Content type
{
  • "field": "string",
  • "value": "string"
}

Get field edit view

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
field
required
string

Responses

Response samples

Content type
{
  • "field": "string",
  • "value": "string"
}

Update a single metadata field

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).

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
field
required
string
Request Body schema:
required
value
required
string

Responses

Request samples

Content type
{
  • "value": "string"
}

Response samples

Content type
{
  • "status": "string",
  • "field": "string",
  • "value": "string"
}

Clear a single metadata field

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
field
required
string

Responses

Response samples

Content type
{
  • "status": "string",
  • "field": "string"
}

Compare field value across providers

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
field
required
string

Responses

Response samples

Content type
{
  • "field": "string",
  • "results": [
    ]
}

Clear all band members

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
{
  • "status": "string"
}

Save band members from provider data

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema: application/json
required
Array
name
string
mbid
string
instruments
Array of strings
vocal_type
string
date_joined
string
date_left
string
is_active
boolean

Responses

Request samples

Content type
application/json
[
  • {
    }
]

Response samples

Content type
{
  • "status": "string"
}

Rename the artist's on-disk directory

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema:
required
new_dirname
required
string

New leaf directory name. Must be a single path segment with no separators ("/" or ""); "." and ".." are also rejected.

Responses

Request samples

Content type
{
  • "new_dirname": "string"
}

Response samples

Content type
application/json
{
  • "status": "renamed",
  • "new_path": "string"
}

Refresh artist metadata from providers

Triggers a full metadata refresh. If the artist has no MusicBrainz ID, returns a disambiguation prompt instead.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
{
  • "status": "string",
  • "sources": [
    ],
  • "warning": "string"
}

Search providers for disambiguation

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema:
required
query
required
string

Responses

Request samples

Content type
{
  • "query": "string"
}

Response samples

Content type
{
  • "results": [
    ]
}

Get metadata diffs between Stillwater and MusicBrainz

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "artist_id": "string",
  • "musicbrainz_id": "string",
  • "diffs": [
    ],
  • "contribution_mode": "disabled",
  • "last_fetched_at": "2019-08-24T14:15:22Z"
}

Identify or re-identify an artist via MusicBrainz

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).

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema: application/x-www-form-urlencoded
optional
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.

Responses

Response samples

Content type
{
  • "status": "string",
  • "artist": "string",
  • "message": "string"
}

Run all enabled rules scoped to a single artist

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
{
  • "violations_found": 0,
  • "dashboard_url": "string"
}

Rules

Evaluate artist health

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "artist_id": "string",
  • "artist_name": "string",
  • "violations": [
    ],
  • "rules_passed": 0,
  • "rules_total": 0,
  • "health_score": 0,
  • "warning": "string"
}

List rules

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "rules": [
    ],
  • "has_local_library": true
}

Update rule

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema: application/json
required
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

Responses

Request samples

Content type
application/json
{
  • "enabled": true,
  • "automation_mode": "auto",
  • "config": { }
}

Response samples

Content type
application/json
{ }

Start async evaluation of a single rule

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
query Parameters
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.

Responses

Response samples

Content type
application/json
{
  • "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"
}

Run all enabled rules (async)

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
query Parameters
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.

Responses

Response samples

Content type
application/json
{
  • "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"
}

Get async rule-run status

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "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"
}

Get rule scheduler status

Returns the current state of the rule evaluation scheduler.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "last_evaluation_at": "2019-08-24T14:15:22Z",
  • "interval_minutes": 0,
  • "next_evaluation_at": "2019-08-24T14:15:22Z",
  • "scheduler_enabled": true
}

Get classical music handling mode

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "mode": "skip"
}

Set classical music handling mode

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
required
mode
required
string
Enum: "skip" "composer" "performer"

Responses

Request samples

Content type
application/json
{
  • "mode": "skip"
}

Response samples

Content type
application/json
{
  • "mode": "string"
}

Run all enabled rules scoped to a single artist

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
{
  • "violations_found": 0,
  • "dashboard_url": "string"
}

History

List artist metadata change history

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Artist ID.

query Parameters
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.

Responses

Response samples

Content type
application/json
{
  • "changes": [
    ],
  • "total": 0,
  • "limit": 0,
  • "offset": 0
}

Revert a metadata change

Restores the old value from a metadata_changes record back to the artist field. Creates a new history entry with source "revert".

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Metadata change record ID.

Responses

Response samples

Content type
{
  • "reverted": true,
  • "change_id": "string"
}

List global metadata change history

Returns paginated metadata change records across all artists, ordered by most recent first.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
query Parameters
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.

Responses

Response samples

Content type
application/json
{
  • "changes": [
    ],
  • "total": 0,
  • "limit": 0,
  • "offset": 0
}

Aliases

List artist aliases

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Add alias to artist

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema:
required
alias
required
string
source
string

Responses

Request samples

Content type
{
  • "alias": "string",
  • "source": "string"
}

Response samples

Content type
application/json
{ }

Remove alias

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
aliasId
required
string

Responses

Response samples

Content type
application/json
{
  • "status": "string"
}

Images

Upload image file

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
query Parameters
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.

Request Body schema: multipart/form-data
required
file
required
string <binary>
type
required
string
Enum: "thumb" "fanart" "logo" "banner"

Responses

Response samples

Content type
application/json
{
  • "status": "string",
  • "saved": [
    ],
  • "type": "string",
  • "sync_warnings": [
    ],
  • "count": 0,
  • "needs_crop": true,
  • "required_ratio": 0,
  • "actual_ratio": 0,
  • "width": 0,
  • "height": 0,
  • "image_data": "string"
}

Fetch image from URL

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
query Parameters
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.

Request Body schema:
required
url
required
string <uri>
type
required
string
Enum: "thumb" "fanart" "logo" "banner"

Responses

Request samples

Content type
{}

Response samples

Content type
application/json
{
  • "status": "string",
  • "saved": [
    ],
  • "type": "string",
  • "sync_warnings": [
    ],
  • "count": 0,
  • "needs_crop": true,
  • "required_ratio": 0,
  • "actual_ratio": 0,
  • "width": 0,
  • "height": 0,
  • "image_data": "string"
}

Search providers for images

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
query Parameters
type
string
Enum: "thumb" "fanart" "logo" "banner"
sort
string
Default: "likes"
Enum: "likes" "resolution"

Sort order for results. Defaults to likes descending.

Responses

Response samples

Content type
{
  • "images": [
    ],
  • "errors": [
    ]
}

Crop and save image

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema: application/json
required
image_data
required
string

Base64-encoded image data

type
required
string
Enum: "thumb" "fanart" "logo" "banner"
x
integer
y
integer
width
integer
height
integer

Responses

Request samples

Content type
application/json
{
  • "image_data": "string",
  • "type": "thumb",
  • "x": 0,
  • "y": 0,
  • "width": 0,
  • "height": 0
}

Response samples

Content type
application/json
{
  • "status": "string",
  • "saved": [
    ],
  • "type": "string",
  • "sync_warnings": [
    ]
}

Search web for artist images

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
query Parameters
type
required
string
Enum: "thumb" "fanart" "logo" "banner"
sort
string
Default: "likes"
Enum: "likes" "resolution"

Sort order for results. Defaults to likes descending.

Responses

Response samples

Content type
{
  • "images": [
    ]
}

List all fanart images with metadata

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
query Parameters
management
boolean

When true and request is HTMX, returns the management gallery variant with per-slot controls

Responses

Response samples

Content type
[
  • {
    }
]

Serve fanart image by index

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
index
required
integer

Responses

Response samples

Content type
application/json
{
  • "error": "string",
  • "details": [
    ]
}

Delete selected fanart by indices

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema: application/json
required
indices
required
Array of integers

Responses

Request samples

Content type
application/json
{
  • "indices": [
    ]
}

Response samples

Content type
application/json
{
  • "status": "string",
  • "deleted": [
    ],
  • "count": 0,
  • "sync_warnings": [
    ]
}

Fetch multiple fanart images from URLs

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema: application/json
required
urls
required
Array of strings <uri> <= 20 items [ items <uri > ]

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "status": "string",
  • "saved": [
    ],
  • "errors": [
    ],
  • "count": 0,
  • "sync_warnings": [
    ]
}

Reorder local fanart files

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema: application/json
required
order
required
Array of integers

Permutation of current slot indices defining the new order. order[i] = current slot that becomes new slot i.

Responses

Request samples

Content type
application/json
{
  • "order": [
    ]
}

Response samples

Content type
application/json
{
  • "status": "string",
  • "count": 0,
  • "sync_warnings": [
    ]
}

Assign a platform backdrop to a local fanart slot

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
slot
required
integer
Request Body schema: application/json
required
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

Responses

Request samples

Content type
application/json
{
  • "connection_id": "string",
  • "platform_index": 0
}

Response samples

Content type
application/json
{
  • "status": "string",
  • "slot": 0,
  • "count": 0,
  • "sync_warnings": [
    ]
}

Delete a single fanart slot and renumber remaining

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
slot
required
integer

Responses

Response samples

Content type
application/json
{
  • "status": "string",
  • "deleted": "string",
  • "count": 0,
  • "sync_warnings": [
    ]
}

List available backdrops from all connected platforms

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
{
  • "connections": [
    ]
}

Proxy a platform backdrop image through Stillwater

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
connectionId
required
string
index
required
integer

Responses

Response samples

Content type
application/json
{
  • "error": "string",
  • "details": [
    ]
}

Get per-image sync state for fanart slots across connected platforms

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
{
  • "slots": [
    ],
  • "state": "string"
}

Random artist backdrop redirect

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Serve artist image file

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
type
required
string
Enum: "thumb" "fanart" "logo" "banner"

Responses

Response samples

Content type
application/json
{
  • "error": "string",
  • "details": [
    ]
}

Get image metadata (dimensions, size)

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
type
required
string
Enum: "thumb" "fanart" "logo" "banner"

Responses

Response samples

Content type
{
  • "type": "string",
  • "filename": "string",
  • "width": 0,
  • "height": 0,
  • "size": 0,
  • "modified": "2019-08-24T14:15:22Z"
}

Delete artist image

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
type
required
string
Enum: "thumb" "fanart" "logo" "banner"

Responses

Response samples

Content type
{
  • "status": "string",
  • "deleted": [
    ],
  • "sync_warnings": [
    ]
}

NFO

Get NFO field-level diff

For pathless artists (no filesystem path), returns a virtual NFO generated from database fields instead of reading from disk.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
{ }

Check for external NFO modifications

For pathless artists, always returns no conflict since there is no on-disk NFO file to be modified externally.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "has_conflict": true,
  • "reason": "string",
  • "external_writer": "string",
  • "last_modified": "2019-08-24T14:15:22Z"
}

Platform IDs

List platform artist ID mappings

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Set platform artist ID mapping

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
connectionId
required
string
Request Body schema: application/json
required
platform_artist_id
required
string

Responses

Request samples

Content type
application/json
{
  • "platform_artist_id": "string"
}

Response samples

Content type
application/json
{
  • "status": "string"
}

Delete platform artist ID mapping

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
connectionId
required
string

Responses

Response samples

Content type
application/json
{
  • "status": "string"
}

Platform State

Get artist platform state comparison card

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
query Parameters
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.

Responses

Response samples

Content type
{
  • "error": "string",
  • "details": [
    ]
}

Pull artist metadata from platform

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
query Parameters
connection_id
required
string

Responses

Response samples

Content type
application/json
{
  • "status": "string",
  • "updated": [
    ]
}

Push

Push metadata to platform

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema: application/json
required
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.

Responses

Request samples

Content type
application/json
{
  • "connection_id": "string",
  • "platform_artist_id": "string"
}

Response samples

Content type
application/json
{
  • "status": "string"
}

Push images to platform

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema: application/json
required
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)

Responses

Request samples

Content type
application/json
{
  • "connection_id": "string",
  • "platform_artist_id": "string",
  • "image_types": [
    ]
}

Response samples

Content type
application/json
{
  • "uploaded": [
    ],
  • "errors": [
    ]
}

Delete a single image from a platform

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
type
required
string
Enum: "thumb" "fanart" "logo" "banner"
Request Body schema: application/json
required
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.

Responses

Request samples

Content type
application/json
{
  • "connection_id": "string",
  • "platform_artist_id": "string"
}

Response samples

Content type
application/json
{
  • "status": "deleted"
}

Platforms

List platform profiles

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
[
  • { }
]

Create platform profile

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
required
name
required
string
nfo_enabled
boolean
nfo_format
string
Enum: "kodi" "jellyfin"
image_naming
object

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "nfo_enabled": true,
  • "nfo_format": "kodi",
  • "image_naming": { }
}

Response samples

Content type
application/json
{ }

Get platform profile

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{ }

Update platform profile

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema: application/json
required
name
string
nfo_enabled
boolean
nfo_format
string
image_naming
object

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "nfo_enabled": true,
  • "nfo_format": "string",
  • "image_naming": { }
}

Response samples

Content type
application/json
{ }

Delete platform profile

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "status": "string"
}

Set platform as active

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "status": "string"
}

Connections

List connections

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Create connection

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema:
required
name
required
string
type
required
string
Enum: "emby" "jellyfin" "lidarr"
url
required
string
api_key
string
enabled
boolean
skip_test
boolean

Responses

Request samples

Content type
{
  • "name": "string",
  • "type": "emby",
  • "url": "string",
  • "api_key": "string",
  • "enabled": true,
  • "skip_test": true
}

Response samples

Content type
application/json
{
  • "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
}

Get connection

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "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
}

Update connection

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema: application/json
required
name
string
type
string
url
string
api_key
string
enabled
boolean

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "type": "string",
  • "url": "string",
  • "api_key": "string",
  • "enabled": true
}

Response samples

Content type
application/json
{
  • "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
}

Delete connection

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "status": "string"
}

Test connection

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "status": "ok",
  • "message": "string",
  • "drift_warnings": [
    ]
}

Check for NFO/image write conflicts

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
{
  • "has_risk": true,
  • "risks": [
    ]
}

Update connection feature flags

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema: application/json
required
feature_library_import
boolean
feature_nfo_write
boolean
feature_image_write
boolean
feature_metadata_push
boolean
feature_trigger_refresh
boolean

Responses

Request samples

Content type
application/json
{
  • "feature_library_import": true,
  • "feature_nfo_write": true,
  • "feature_image_write": true,
  • "feature_metadata_push": true,
  • "feature_trigger_refresh": true
}

Response samples

Content type
application/json
{
  • "status": "string"
}

Get platform fetcher/saver settings

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
Example
{
  • "connection_type": "emby",
  • "libraries": [
    ],
  • "note": "string"
}

Disable conflicting platform settings

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).

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema: application/json
required
library_id
string

Required for Emby/Jellyfin connections.

consumer_id
integer

Required for Lidarr connections.

Responses

Request samples

Content type
application/json
{
  • "library_id": "string",
  • "consumer_id": 0
}

Response samples

Content type
application/json
{
  • "status": "string",
  • "note": "string"
}

Toggle "Let Stillwater manage artwork and NFO files on this server"

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema:
required

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

Responses

Request samples

Content type
{
  • "enabled": true
}

Response samples

Content type
application/json
{
  • "connection_id": "string",
  • "feature_manage_server_files": true
}

Get the current conflict ledger

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).

Authorizations:
cookieAuthbearerAuthapiKeyAuth
query Parameters
refresh
string
Value: "1"

Responses

Response samples

Content type
application/json
{
  • "generated_at": "2019-08-24T14:15:22Z",
  • "connections": [
    ],
  • "round_trips": [
    ],
  • "foreign_files": {
    }
}

Rendered HTML body for the OOBE conflict pre-flight step

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
query Parameters
refresh
string
Value: "1"

Responses

Rendered HTML partial for the conflict banner

HTMX consumers swap the response into #conflict-banner. Empty-body responses (204) indicate detection is not available; an HTML fragment is returned otherwise.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
query Parameters
refresh
string
Value: "1"

Responses

Get platform settings management summary

Returns an aggregate summary of platform fetcher/saver management status for a connection. Used for connection card badges.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "total_libraries": 0,
  • "managed_libraries": 0,
  • "total_consumers": 0,
  • "has_conflicts": true,
  • "needs_lockdata": true
}

Discover libraries on connection

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
[
  • {
    }
]

Import libraries from connection

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema: application/json
required
required
Array of objects

Responses

Request samples

Content type
application/json
{
  • "libraries": [
    ]
}

Response samples

Content type
application/json
[
  • {
    }
]

Populate artists from connection into library

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
libId
required
string

Responses

Response samples

Content type
application/json
{
  • "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"
}

Scan library via connection API

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
libId
required
string

Responses

Response samples

Content type
application/json
{
  • "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"
}

Providers

List providers and API key status

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "providers": [
    ]
}

Set provider API key

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
name
required
string
Request Body schema:
required
api_key
required
string
skip_test
boolean

Responses

Request samples

Content type
{
  • "api_key": "string",
  • "skip_test": true
}

Response samples

Content type
{
  • "status": "string"
}

Delete provider API key

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
name
required
string

Responses

Response samples

Content type
application/json
{
  • "status": "string"
}

Set provider mirror URL

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
name
required
string
Request Body schema:
required
base_url
required
string

Mirror base URL (must be http or https)

rate_limit
number

Requests per second (default 10, max 100)

Responses

Request samples

Content type
{
  • "base_url": "string",
  • "rate_limit": 0
}

Response samples

Content type
{
  • "status": "string",
  • "test": "ok",
  • "test_error": "string"
}

Delete provider mirror configuration

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
name
required
string

Responses

Response samples

Content type
{
  • "status": "string"
}

Test provider connection

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
name
required
string

Responses

Response samples

Content type
{
  • "status": "ok",
  • "error": "string",
  • "status_persisted": true
}

Get provider priorities

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "priorities": [
    ]
}

Set provider priorities

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
required
required
Array of objects

Responses

Request samples

Content type
application/json
{
  • "priorities": [
    ]
}

Response samples

Content type
{
  • "status": "string"
}

Reset provider priorities to defaults

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
{
  • "status": "string",
  • "priorities": [
    ]
}

Search providers for artist

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
required
name
required
string

Responses

Request samples

Content type
application/json
{
  • "name": "string"
}

Response samples

Content type
application/json
{
  • "results": [
    ]
}

Fetch metadata from providers

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
required
mbid
string
name
string

Responses

Request samples

Content type
application/json
{
  • "mbid": "string",
  • "name": "string"
}

Response samples

Content type
application/json
{ }

Toggle provider for a metadata field

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
field
required
string
provider
required
string

Responses

Response samples

Content type
{
  • "status": "string"
}

List web search image providers

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "providers": [
    ]
}

Enable or disable a web search provider

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
name
required
string
Request Body schema:
required
enabled
required
boolean

Responses

Request samples

Content type
{
  • "enabled": true
}

Response samples

Content type
{
  • "status": "string"
}

Bulk Operations

Start bulk metadata fetch

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
mode
string
Default: "prompt_no_match"
Enum: "prompt_no_match" "yolo" "auto_exact" "auto_similar"

Responses

Request samples

Content type
application/json
{
  • "mode": "prompt_no_match"
}

Response samples

Content type
application/json
{ }

Start bulk image fetch

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
mode
string
Default: "prompt_no_match"

Responses

Request samples

Content type
application/json
{
  • "mode": "prompt_no_match"
}

Response samples

Content type
application/json
{ }

List recent bulk jobs

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "jobs": [
    ]
}

Get bulk job details

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "job": { },
  • "items": [
    ]
}

Cancel running bulk job

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "status": "string"
}

Start a bulk action over an explicit artist ID list

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
required
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).

Responses

Request samples

Content type
application/json
{
  • "action": "run_rules",
  • "ids": [
    ]
}

Response samples

Content type
application/json
{
  • "status": "running",
  • "action": "run_rules",
  • "total": 0
}

Get the current bulk-action progress

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "status": "idle",
  • "action": "run_rules",
  • "total": 0,
  • "processed": 0,
  • "succeeded": 0,
  • "skipped": 0,
  • "failed": 0,
  • "current_name": "string"
}

Cancel the in-flight bulk action

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "status": "canceling"
}

Start bulk artist identification

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
optional
library_id
string

Optional library ID to scope the identification to a single library.

Responses

Request samples

Content type
application/json
{
  • "library_id": "string"
}

Response samples

Content type
application/json
{
  • "status": "completed",
  • "message": "string",
  • "total": 0
}

Get bulk identification progress

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "status": "idle",
  • "total": 0,
  • "processed": 0,
  • "auto_linked": 0,
  • "queued": 0,
  • "unmatched": 0,
  • "failed": 0,
  • "current_name": "string",
  • "review_queue": [
    ]
}

Cancel bulk identification

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "status": "idle",
  • "message": "string"
}

Start an interactive re-identify wizard session

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
required
ids
required
Array of strings [ 1 .. 1000 ] items [ items^[A-Za-z0-9_-]{1,64}$ ]

Responses

Request samples

Content type
application/json
{
  • "ids": [
    ]
}

Response samples

Content type
application/json
{
  • "session_id": "string",
  • "total": 0,
  • "index": 0
}

Accept a candidate for the current wizard step

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
sid
required
string
idx
required
integer
Request Body schema:
required
mbid
required
string
discogs_id
string

Responses

Request samples

Content type
{
  • "mbid": "string",
  • "discogs_id": "string"
}

Response samples

Content type
application/json
No sample

Skip the current wizard step without changes

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
sid
required
string
idx
required
integer

Responses

Response samples

Content type
application/json
No sample

Mark the current wizard step as no-match

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
sid
required
string
idx
required
integer

Responses

Response samples

Content type
application/json
No sample

End the wizard early and leave remaining artists in review

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
sid
required
string

Responses

Response samples

Content type
application/json
{
  • "status": "saved",
  • "accepted": 0,
  • "skipped": 0,
  • "declined": 0,
  • "leftover": 0
}

Reports

Get library health summary

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
{
  • "score": 0,
  • "total_artists": 0,
  • "compliant_artists": 0,
  • "missing_nfo": 0,
  • "missing_thumb": 0,
  • "missing_fanart": 0,
  • "missing_mbid": 0,
  • "top_violations": [
    ]
}

Get health history for charting

Authorizations:
cookieAuthbearerAuthapiKeyAuth
query Parameters
from
string <date>
to
string <date>

Responses

Response samples

Content type
application/json
{
  • "history": [
    ]
}

Get compliance report

Authorizations:
cookieAuthbearerAuthapiKeyAuth
query Parameters
page
integer
Default: 1
page_size
integer
Default: 50
search
string
filter
string
status
string
Enum: "compliant" "non_compliant"

Responses

Response samples

Content type
application/json
{
  • "rows": [
    ],
  • "total": 0,
  • "page": 0,
  • "page_size": 0
}

Get daily violation creation and resolution counts

Authorizations:
cookieAuthbearerAuthapiKeyAuth
query Parameters
days
integer [ 1 .. 365 ]
Default: 30

Number of past days to include. Values outside 1-365 default to 30.

Responses

Response samples

Content type
application/json
{
  • "trend": [
    ]
}

Get per-library health breakdown

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
{
  • "libraries": [
    ],
  • "overall": { }
}

Export compliance report as CSV

Authorizations:
cookieAuthbearerAuthapiKeyAuth
query Parameters
sort
string
Default: "name"
order
string
Enum: "asc" "desc"
search
string
filter
string
library_id
string
status
string
Enum: "compliant" "non_compliant"
health_min
integer
health_max
integer

Responses

Get aggregate metadata completeness report

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
query Parameters
library_id
string

Scope the report to a specific library. Omit to include all libraries.

Responses

Response samples

Content type
{
  • "overall_score": 0.1,
  • "total_artists": 0,
  • "field_coverage": [
    ],
  • "library_coverage": [
    ],
  • "lowest_completeness": [
    ]
}

Webhooks

List webhooks

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Create webhook

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema:
required
name
required
string
url
required
string
type
string
events
Array of strings
enabled
boolean

Responses

Request samples

Content type
{
  • "name": "string",
  • "url": "string",
  • "type": "string",
  • "events": [
    ],
  • "enabled": true
}

Response samples

Content type
application/json
{
  • "id": "string",
  • "name": "string",
  • "url": "string",
  • "type": "string",
  • "events": [
    ],
  • "enabled": true,
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Get webhook

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "id": "string",
  • "name": "string",
  • "url": "string",
  • "type": "string",
  • "events": [
    ],
  • "enabled": true,
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Update webhook

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema: application/json
required
name
string
url
string
type
string
events
Array of strings
enabled
boolean

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "url": "string",
  • "type": "string",
  • "events": [
    ],
  • "enabled": true
}

Response samples

Content type
application/json
{
  • "id": "string",
  • "name": "string",
  • "url": "string",
  • "type": "string",
  • "events": [
    ],
  • "enabled": true,
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Delete webhook

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "status": "string"
}

Send test webhook event

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "status": "string"
}

Receive Lidarr webhook event

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).

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
required
eventType
required
string

Lidarr event type (Test, ArtistAdded, Grab, Download, AlbumImport)

object

Responses

Request samples

Content type
application/json
{
  • "eventType": "string",
  • "artist": {
    }
}

Response samples

Content type
application/json
{
  • "status": "string"
}

Receive Emby webhook event

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).

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
required
Event
required
string

Emby event type (system.notificationtest, library.new, item.updated, library.changed)

object

Responses

Request samples

Content type
application/json
{
  • "Event": "string",
  • "Item": {
    }
}

Response samples

Content type
application/json
{
  • "status": "string"
}

Receive Jellyfin webhook event

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).

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
required
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

Responses

Request samples

Content type
application/json
{
  • "NotificationType": "string",
  • "ItemId": "string",
  • "ItemType": "string",
  • "Name": "string",
  • "Artist": "string",
  • "Provider_musicbrainzalbumartist": "string"
}

Response samples

Content type
application/json
{
  • "status": "string"
}

SharedFilesystem

Get shared-filesystem overlap status

Returns information about libraries with shared-filesystem overlaps and dismiss state.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "has_overlaps": true,
  • "libraries": [
    ],
  • "dismissed": true,
  • "image_fetcher_warnings": [
    ]
}

Dismiss the shared-filesystem warning permanently

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "status": "string"
}

Get current shared-filesystem status count

Returns the current count of libraries whose shared-filesystem status is set by evidence.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "shared_libraries": 0
}

Filesystem

Browse filesystem directories

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
query Parameters
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.

Responses

Response samples

Content type
application/json
{
  • "path": "string",
  • "entries": [
    ],
  • "parent": "string"
}

Settings

Get all settings

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "property1": "string",
  • "property2": "string"
}

Update settings

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).

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
required
property name*
additional property
string

Responses

Request samples

Content type
application/json
{
  • "property1": "string",
  • "property2": "string"
}

Response samples

Content type
application/json
{
  • "status": "string"
}

Get logging configuration

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "level": "trace",
  • "format": "text",
  • "file_path": "string",
  • "file_max_size_mb": 0,
  • "file_max_files": 0,
  • "file_max_age_days": 0
}

Update logging configuration

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema:
required
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.

Responses

Request samples

Content type
{
  • "level": "trace",
  • "format": "text",
  • "file_path": "string",
  • "file_max_size_mb": 0,
  • "file_max_files": 0,
  • "file_max_age_days": 0
}

Response samples

Content type
{
  • "level": "trace",
  • "format": "text",
  • "file_path": "string",
  • "file_max_size_mb": 0,
  • "file_max_files": 0,
  • "file_max_age_days": 0
}

Get database maintenance status

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
{
  • "db_file_size": 0,
  • "wal_file_size": 0,
  • "page_count": 0,
  • "page_size": 0,
  • "last_optimize_at": "2019-08-24T14:15:22Z",
  • "schedule_enabled": true,
  • "schedule_interval_hours": 0
}

Trigger database optimization

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
{
  • "status": "string"
}

Trigger database vacuum

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
{
  • "status": "string"
}

Configure maintenance schedule

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
required
enabled
required
boolean
interval_hours
integer

Responses

Request samples

Content type
application/json
{
  • "enabled": true,
  • "interval_hours": 0
}

Response samples

Content type
{
  • "status": "string"
}

Export settings as encrypted file

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema:
required
passphrase
required
string

Responses

Request samples

Content type
{
  • "passphrase": "string"
}

Response samples

Content type
application/json
{
  • "version": "string",
  • "app_version": "string",
  • "created_at": "2019-08-24T14:15:22Z",
  • "salt": "string",
  • "data": "string",
  • "summary": {
    }
}

Import settings from encrypted file

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema:
required
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 summary block is informational only on the import path.

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.

Responses

Request samples

Content type
{
  • "passphrase": "string",
  • "envelope": {
    },
  • "admin_fallback_tokens": true
}

Response samples

Content type
{
  • "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
}

Get NFO output field mapping

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "default_behavior": true,
  • "moods_as_styles": true,
  • "genre_sources": [
    ],
  • "advanced_remap": {
    }
}

Update NFO output field mapping

Updates the NFO field mapping configuration. Controls how genre, style, and mood source categories map to NFO XML elements.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
required
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.

Responses

Request samples

Content type
application/json
{
  • "default_behavior": true,
  • "moods_as_styles": true,
  • "genre_sources": [
    ],
  • "advanced_remap": {
    }
}

Response samples

Content type
application/json
{
  • "default_behavior": true,
  • "moods_as_styles": true,
  • "genre_sources": [
    ],
  • "advanced_remap": {
    }
}

Get image cache statistics

Returns the current size, file count, and artist count for the pathless-artist image cache.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "size_bytes": 0,
  • "file_count": 0,
  • "artist_count": 0
}

Clear the image cache

Deletes all files in the pathless-artist image cache and resets image existence flags. Uses best-effort deletion.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "files_deleted": 0,
  • "bytes_freed": 0
}

Preferences

Get all user preferences

Returns all preference keys for the authenticated user, merged with defaults. Preferences are per-user appearance settings (theme, sidebar state, typography, etc.).

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "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"
}

Get a single user preference

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".

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
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.

Responses

Response samples

Content type
application/json
{
  • "key": "string",
  • "value": "string"
}

Update a user preference

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
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".

Request Body schema: application/json
required
value
required
string

Responses

Request samples

Content type
application/json
{
  • "value": "string"
}

Response samples

Content type
application/json
{
  • "key": "string",
  • "value": "string"
}

Backup

Create database backup

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
{
  • "filename": "string",
  • "size": 0,
  • "created_at": "2019-08-24T14:15:22Z"
}

List backup files

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
[
  • {
    }
]

Download backup file

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
filename
required
string

Responses

Delete a backup file

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
filename
required
string

Responses

Response samples

Content type
{
  • "status": "string"
}

Scanner

Trigger filesystem scan

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{ }

Get scan status

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{ }

Docs

Interactive API reference

Responses

OpenAPI specification

Responses

Libraries

List libraries

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Create library

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema:
required
name
required
string
path
string
type
string
Default: "regular"
Enum: "regular" "classical"

Responses

Request samples

Content type
{
  • "name": "string",
  • "path": "string",
  • "type": "regular"
}

Response samples

Content type
application/json
{
  • "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"
}

Get library

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "library": {
    },
  • "artist_count": 0
}

Update library

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema:
required
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 true. Off by default; opt-in per library (issue

Responses

Request samples

Content type
{
  • "name": "string",
  • "path": "string",
  • "type": "regular",
  • "fs_watch": 0,
  • "fs_poll_interval": 0,
  • "nfo_lock_data": true
}

Response samples

Content type
application/json
{
  • "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"
}

Delete library

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
query Parameters
deleteArtists
boolean
Default: false

When true, cascade-delete artists instead of dereferencing them

Responses

Response samples

Content type
application/json
{
  • "status": "string"
}

Get library operation status

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
libId
required
string

Responses

Response samples

Content type
application/json
{
  • "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"
}

Scraper

Get global scraper configuration

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{ }

Update global scraper configuration

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
required
object

Responses

Request samples

Content type
application/json
{ }

Response samples

Content type
application/json
{
  • "status": "string"
}

Get connection scraper config overrides

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "config": { },
  • "raw": { },
  • "overrides": { }
}

Update connection scraper config overrides

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema: application/json
required
object

Responses

Request samples

Content type
application/json
{ }

Response samples

Content type
application/json
{
  • "status": "string"
}

Reset connection scraper config to global defaults

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "status": "string"
}

List scraper providers with capabilities

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "providers": [
    ]
}

Notifications

Get violation counts by severity

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "error": 0,
  • "warning": 0,
  • "info": 0,
  • "total": 0
}

Get notification badge HTML fragment

Returns an HTML fragment for HTMX polling. Not a JSON endpoint.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

List rule violations with optional filtering and sorting

Authorizations:
cookieAuthbearerAuthapiKeyAuth
query Parameters
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

Responses

Response samples

Content type
application/json
{
  • "violations": [
    ],
  • "count": 0
}

Dismiss a violation

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "status": "string"
}

Resolve a violation

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "status": "string"
}

Apply an image candidate from a pending-choice violation

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string
Request Body schema: application/json
required
url
required
string
image_type
required
string
Enum: "thumb" "fanart" "logo" "banner"

Responses

Request samples

Content type
application/json
{
  • "url": "string",
  • "image_type": "thumb"
}

Response samples

Content type
application/json
{
  • "status": "string"
}

Bulk dismiss violations

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
ids
Array of strings

Violation IDs to dismiss (omit to dismiss all)

Responses

Request samples

Content type
application/json
{
  • "ids": [
    ]
}

Response samples

Content type
application/json
{
  • "dismissed": 0
}

Apply recommended fix for a violation

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "status": "fixed",
  • "message": "string",
  • "undo_id": "string",
  • "undo_expires_in": 0
}

Revert a recently applied fix

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
undoId
required
string

Opaque undo token returned by the fix endpoint

Responses

Response samples

Content type
application/json
{
  • "status": "reverted",
  • "message": "string"
}

Start async bulk fix for fixable violations

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
ids
Array of strings

Optional subset of violation IDs. Omit to fix all fixable.

Responses

Request samples

Content type
application/json
{
  • "ids": [
    ]
}

Response samples

Content type
application/json
{
  • "status": "completed",
  • "message": "string",
  • "total": 0
}

Get fix-all operation progress

Returns the current state of the most recent fix-all operation.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "status": "idle",
  • "total": 0,
  • "processed": 0,
  • "fixed": 0,
  • "skipped": 0,
  • "failed": 0
}

Export filtered violations as CSV or JSON

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
query Parameters
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.

Responses

Response samples

Content type
No sample

Clear old resolved violations

Permanently deletes resolved violations older than 7 days.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "status": "string"
}

Logs

List available log files

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Delete rotated log files

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "deleted": 0,
  • "bytes_freed": 0
}

Get recent log entries

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
query Parameters
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)

Responses

Response samples

Content type
[
  • {
    }
]

Clear log buffer

Removes all entries from the in-memory ring buffer. Returns JSON for API clients or an HTML fragment when the HX-Request header is set.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
{
  • "status": "cleared"
}

Updates

Check for updates

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "current": "v0.9.6",
  • "latest": "v1.0.0",
  • "channel": "stable",
  • "update_available": true,
  • "release_url": "string",
  • "published_at": "2019-08-24T14:15:22Z"
}

Get update status

Returns the current state of the update lifecycle: idle, checking, downloading, applying, or error. Poll this endpoint after POST /updates/apply to track progress.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "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"
}

Apply update

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
optional
object

Responses

Request samples

Content type
application/json
{ }

Response samples

Content type
application/json
{
  • "status": "string"
}

Get update configuration

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).

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "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": [
    ]
}

Set update configuration

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
}
Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
required
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 enabled is also true), the updater periodically checks for new releases in the background at check_interval_hours. Default: false.

auto_update
boolean

When true (and enabled, auto_check are also true), the scheduler calls Apply automatically after a successful check finds a newer release. Docker hosts ignore this flag (the scheduler no-ops on Docker; orchestration handles container image refresh). The skip-this-version list (see /updates/skips) gates which candidate tags are eligible. Default: false.

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.

Responses

Request samples

Content type
application/json
{
  • "channel": "stable",
  • "enabled": true,
  • "auto_check": true,
  • "auto_update": true,
  • "check_interval_hours": 0
}

Response samples

Content type
application/json
{
  • "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": [
    ]
}

List skipped release tags

Returns the persisted skip list. The scheduler reads this list on every tick and short-circuits AutoUpdate when the candidate tag is present.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "skipped_versions": [
    ]
}

Append a release tag to the skip list

Idempotent: a tag already present is a no-op. The scheduler honors the post-write list on the next tick.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
Request Body schema: application/json
required
version
required
string non-empty

Release tag to skip (e.g. "v1.2.3" or "nightly-20260101").

Responses

Request samples

Content type
application/json
{
  • "version": "string"
}

Response samples

Content type
application/json
{
  • "skipped_versions": [
    ]
}

Remove a release tag from the skip list

Idempotent: removing a tag that is not present is a no-op.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
version
required
string

Responses

Response samples

Content type
application/json
{
  • "error": "string",
  • "details": [
    ]
}

Events

SSE event stream

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":{}}
Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "error": "string",
  • "details": [
    ]
}

Foreign Files

List detected foreign image files

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "count": 0,
  • "items": [
    ]
}

Bulk allowlist every active foreign file globally

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "error": "string",
  • "details": [
    ]
}

Allowlist a single ledger row (artist-scoped)

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "error": "string",
  • "details": [
    ]
}

Delete the file from disk and clear the ledger row

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.

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "error": "string",
  • "details": [
    ]
}

List foreign-file allowlist entries

Authorizations:
cookieAuthbearerAuthapiKeyAuth

Responses

Response samples

Content type
application/json
{
  • "count": 0,
  • "items": [
    ]
}

Remove an allowlist entry

Removes an allowlist row, re-enabling foreign-file detection for the matching file_name (artist-scoped or global, depending on the row).

Authorizations:
cookieAuthbearerAuthapiKeyAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "error": "string",
  • "details": [
    ]
}