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.
typeahead_search(query, exclude_ids=None)
¶
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'
|
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
|
recommendations, and sonic coverage). |
Raises:
| Type | Description |
|---|---|
ValueError
|
if |
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 |
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 |
'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 |
required |
target
|
Callable[..., None]
|
blocking callable to run in the worker thread. |
required |
*args
|
object
|
positional arguments forwarded to |
()
|
**kwargs
|
object
|
keyword arguments forwarded to |
{}
|
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'
|
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 |
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 |
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 |