Skip to content

API Handlers

Surface-agnostic orchestration from musicseed-api (musicseed_api.handlers.*). Handlers call core services, manipulate config, manage job lifecycles, and map errors; routes stay thin.

The web UI calls these handlers through HTTP; the CLI and MCP call core services directly. Handlers are framework-free, synchronous functions. Routes parse form/query inputs and the app maps typed core exceptions to HTTP responses.

See the HTTP API guide for URL prefixes, request encoding, and the live OpenAPI schema (/api/openapi.json with musicseed, /openapi.json with musicseed --no-ui). The playlist handlers retain programmatic generate-and-write paths, but HTTP create/populate routes always require approved track_ids.

Recommend

musicseed_api.handlers.recommend

Recommendation orchestration — seed parsing, typeahead, and recommendations.

get_recommendation_presets()

Return the authoritative named presets (see RECOMMENDATION_PRESETS).

parse_seed_ids(raw)

Parse a comma-separated seed-id string into a deduplicated int list.

Search tracks for autocomplete (delegates to the core typeahead service).

run_recommendations(seed_ids, limit=50, method='average', per_seed_limit=30, year_min=None, year_max=None, max_tracks_per_artist=3, min_score=None, weights=None)

Run the recommendation story line for a set of seed track ids.

Delegates to core's get_recommendations unchanged. This handler exists to (a) validate that at least one seed is present before a confusing ValueError reaches the caller, and (b) give the CLI and web surfaces a framework-free callable that doesn't reach into core directly.

Parameters:

Name Type Description Default
seed_ids list[int]

local seed track ids.

required
limit int

maximum number of recommendations to return.

50
method RecommendMethod

"average" or "frequency".

'average'
per_seed_limit int

candidates gathered per seed ("frequency" only).

30
year_min int | None

only recommend tracks released in this year or later.

None
year_max int | None

only recommend tracks released in this year or earlier.

None
max_tracks_per_artist int

artist diversity cap during selection.

3
min_score float | None

drop recommendations below this total score.

None
weights Weights | None

signal weights; defaults to the "balanced" preset.

None

Returns:

Type Description
RecommendationResult

The core RecommendationResult (resolved seeds, scored

RecommendationResult

recommendations, and sonic coverage).

Raises:

Type Description
ValueError

if seed_ids is empty.

Library

musicseed_api.handlers.library

Library orchestration — status and import job runnable.

get_library_status()

Return local library statistics and enrichment coverage.

Returns:

Type Description
LibraryStatus

The core LibraryStatus result model as-is.

run_import_job(job_id)

Job target for the import story line (runs in a JobManager thread).

routes.library.start_import submits this callable; the manager runs it in a daemon thread with a persisted job row. This function drives core's import_library, translating its (current, total, phase) progress into update_progress checkpoints for the UI, then records a complete_job result summary on success (the manager finalizes the row once the target returns).

Cancellation is cooperative: should_cancel asks the job manager on every importer poll, and a cancelled run emits a cancelled checkpoint and returns without recording success.

Parameters:

Name Type Description Default
job_id int

the job row id assigned by the job manager (always the first positional argument, per the manager convention).

required

Discovery

musicseed_api.handlers.discovery

Discovery orchestration — wizard readiness, config overrides, database init.

Handlers take plain Python arguments and return Pydantic models. They never import FastAPI, Starlette, or any HTTP framework — the same handler is callable from the CLI, a JSON route, or the web rendering layer.

wizard_ready(result)

Local metadata import needs source access, not a live Plex HTTP server.

run_discovery(**overrides)

Run local discovery, passing only recognized keys as overrides.

run_plex_discovery(timeout=3.0)

Discover Plex servers: local subnet via GDM/SSDP, plus the Plex account.

Uses the configured Plex token so servers on other subnets (invisible to multicast) are included when credentials exist. This is a separate, opt-in probe — it never runs as part of discover().

extract_overrides(**raw)

Split raw form fields into (discovery_overrides, sticky_form_values).

Blank values are dropped. sticky_form_values excludes secret fields so tokens are never echoed back to the caller.

save_config_overrides(musicseed_db_path='', spotify_client_id='', spotify_client_secret='', listenbrainz_token='', plex_url='', plex_token='', plex_library='', plex_db_path='', plex_db_ssh='', plex_db_ssh_password='', plex_db_ssh_port='')

Persist setup overrides to config without any side effects.

Saves credentials and paths only — never creates the database, starts an import, or runs enrichment. When no Plex token is supplied and none is configured, reads one from the local Plex installation (Preferences.xml / .LocalAdminToken) so a same-machine setup needs no manual token. Returns True when anything changed. Blank fields leave the existing config untouched.

apply_config_and_init_db(musicseed_db_path='', spotify_client_id='', spotify_client_secret='', listenbrainz_token='', plex_url='', plex_token='', plex_library='', plex_db_path='', plex_db_ssh='', plex_db_ssh_password='', plex_db_ssh_port='')

Persist validated setup overrides to config and create the database.

Carries the Plex settings that passed discovery into config so import uses the same values the user confirmed. Blank fields leave the existing config untouched. Raises whatever initialize_database or the config layer raises; callers are expected to catch and map to their own error convention.

Enrichment

musicseed_api.handlers.enrichment

Enrichment orchestration — credential management and enrichment job runnable.

enrich_kind(source)

Job kind for an enrichment source.

Each source gets its own kind (enrich:spotify / enrich:listenbrainz) so the job system's one-active-job-per-kind rule applies per source and the UI can track each provider's job independently.

save_spotify_creds(client_id, client_secret)

Persist Spotify credentials to config. No-op when both are empty.

save_listenbrainz_token(token)

Persist the ListenBrainz user token to config. No-op when empty.

run_enrich_job(job_id, source='spotify')

Job target for the enrichment story line (runs in a JobManager thread).

Mirrors the import target: routes.enrichment submits this callable under a source-specific job kind (enrich:spotify / enrich:listenbrainz); the manager runs it in a daemon thread; this function drives core's enrich_tracks while forwarding its (current, total, message) progress to update_progress, then records a complete_job result summary on success. Cancellation is cooperative via should_cancel; a cancelled run emits a cancelled checkpoint and returns.

Parameters:

Name Type Description Default
job_id int

the job row id assigned by the job manager.

required
source str

enrichment source passed to core's enrich_tracks ("spotify" or "listenbrainz"); defaults to Spotify.

'spotify'

Dashboard

musicseed_api.handlers.dashboard

Dashboard aggregation — combines discovery, library stats, and job state.

get_dashboard_snapshot(check_server=False)

Return the aggregated dashboard snapshot for surfaces.

Parameters:

Name Type Description Default
check_server bool

whether to include the live Plex HTTP probe; defaults to False so frequent dashboard polls never touch Plex.

False

Returns:

Type Description
DashboardSnapshot

The combined discovery, library, and job snapshot.

Jobs

musicseed_api.handlers.jobs

Job orchestration — submission, progress, cancellation, and deletion.

submit_job(kind, target, *args, **kwargs)

Submit target to the in-process job runner; return the new job id.

Entry point of the job-lifecycle story line: the JobManager creates a persisted job row, runs target in a daemon thread as target(job_id, *args, **kwargs), and reconciles its terminal state.

Parameters:

Name Type Description Default
kind str

job kind (see core JobKind); only one active job of any kind is allowed across processes sharing the database.

required
target Callable[..., None]

blocking callable to run in the worker thread.

required
*args object

positional arguments forwarded to target after job_id.

()
**kwargs object

keyword arguments forwarded to target.

{}

Returns:

Type Description
int

The id of the newly created job row.

Raises:

Type Description
JobConflictError

if a job is already active for this database, or the concurrency pool is full.

get_job_progress(job_id)

Return the current snapshot of a job.

Parameters:

Name Type Description Default
job_id int

id of the job row to read.

required

Returns:

Type Description
dict | None

The job's fields as a plain dict, or None for an unknown id.

cancel_job(job_id)

Request cooperative cancellation of a running job.

The worker polls for the request at safe checkpoints and winds itself down; nothing is interrupted forcibly.

Parameters:

Name Type Description Default
job_id int

id of the job to cancel.

required

delete_job(job_id)

Delete a completed job's history entry.

Active jobs cannot be deleted — they must be canceled first and allowed to reach a terminal state.

Parameters:

Name Type Description Default
job_id int

id of the job row to delete.

required

Raises:

Type Description
NotFoundError

if the job does not exist.

JobConflictError

if the job is still active.

Playlists

musicseed_api.handlers.playlists

Playlist orchestration — list, create, and populate Plex playlists.

get_playlists()

List Plex playlists with track counts.

create_playlist_from_seeds(name, seed_ids, limit=50, weights=None, year_min=None, year_max=None, max_tracks_per_artist=3, track_ids=None)

Run the playlist-create story line, from approved IDs or a fresh selection.

When track_ids is provided, the approved selection (seeds + chosen recommendations) is written verbatim in order via core's create_playlist_from_tracks — no re-scoring, no re-filtering. Otherwise a selection is generated from the seeds via core's create_playlist (generate-and-write) and the resulting playlist is reported.

Parameters:

Name Type Description Default
name str

playlist title.

required
seed_ids list[int]

local seed track ids.

required
limit int

maximum recommendations when generating a selection.

50
weights Weights | None

signal weights; defaults to the "balanced" preset.

None
year_min int | None

only recommend tracks released in this year or later.

None
year_max int | None

only recommend tracks released in this year or earlier.

None
max_tracks_per_artist int

artist diversity cap during selection.

3
track_ids list[int] | None

approved local track ids to write verbatim; when set, the recommendation step is skipped.

None

Returns:

Type Description
dict

A wire-ready dict with the playlist name, track count, seed count,

dict

and recommendation count.

preview_populate(playlist_id, limit=10, method='average', weights=None, year_min=None, year_max=None, max_tracks_per_artist=3)

Preview complementary recommendations for an existing playlist.

apply_populate(playlist_id, limit=10, method='average', weights=None, year_min=None, year_max=None, max_tracks_per_artist=3, track_ids=None)

Run the playlist-populate story line and write to Plex.

When track_ids is provided, only that approved selection is appended (generation is skipped); otherwise recommendations are generated from the playlist's matched tracks. Core's populate_playlist validates the whole selection before any Plex write and reports how many tracks were actually added.

Parameters:

Name Type Description Default
playlist_id str

Plex rating key of the playlist to populate.

required
limit int

maximum recommendations to add (generation path only).

10
method PopulateMethod

"average" or "frequency" (generation path only).

'average'
weights Weights | None

signal weights (generation path only).

None
year_min int | None

only recommend tracks released in this year or later.

None
year_max int | None

only recommend tracks released in this year or earlier.

None
max_tracks_per_artist int

artist diversity cap (generation path only).

3
track_ids list[int] | None

approved local track ids to append verbatim.

None

Returns:

Type Description
dict

A wire-ready dict with the playlist identity, match counts, and how

dict

many tracks were added.

Sonic

musicseed_api.handlers.sonic

Sonic analysis orchestration — coverage, refresh, and vector import.

get_sonic_coverage(library_name=None, recent_days=7)

Report Plex sonic analysis coverage for a music library.

Parameters:

Name Type Description Default
library_name str | None

Plex music library to inspect; defaults to the configured library.

None
recent_days int

size of the "recent additions" window in days.

7

Returns:

Type Description
SonicStatusResult

The core SonicStatusResult as-is.

trigger_sonic_refresh(library_name=None, days=7, wait_seconds=900.0)

Trigger a Plex sonic analysis refresh and reset the cached vectors.

Parameters:

Name Type Description Default
library_name str | None

Plex music library to refresh; defaults to the configured library.

None
days int

size of the "recent additions" window in days.

7
wait_seconds float

maximum time to watch the refresh.

900.0

Returns:

Type Description
SonicRefreshResult

The core SonicRefreshResult as-is.

run_sonic_import_job(job_id)

Job target for the sonic-vector import story line (runs in a manager thread).

Drives core's import_plex_sonic — which reads Plex's blobs database once and upserts vectors into the local track_vectors table — while forwarding batch progress to update_progress. On success it records a complete_job result summary; a cooperatively cancelled run emits a cancelled checkpoint and returns.

Parameters:

Name Type Description Default
job_id int

the job row id assigned by the job manager.

required