HTTP API¶
The API returns JSON and delegates to framework-free handlers, which call core services. The web UI consumes HTTP; CLI and MCP use core directly.
Server modes and OpenAPI¶
| Launch mode | JSON base URL | Operation schema |
|---|---|---|
musicseed |
http://127.0.0.1:8789/api |
/api/openapi.json |
musicseed --no-ui |
http://127.0.0.1:8789 |
/openapi.json |
uv run --project api uvicorn musicseed_api.app:app |
http://127.0.0.1:8000 |
/openapi.json |
./scripts/dev.sh API process |
http://127.0.0.1:8789 |
/openapi.json |
musicseed mounts the API at /api even if web/out/ is missing. Its parent app's
/openapi.json does not describe the mounted operations; use /api/openapi.json and
/api/docs. Unprefixed modes expose interactive docs at /docs. Contributor Next.js serves
on port 3000 and proxies browser /api/* requests to the unprefixed API.
The live OpenAPI schema is the request-contract source of truth. Many routes currently return
plain dict/list[dict], so their OpenAPI response schemas are generic; consult the handler
references and route tests for detailed response fields. No separate hand-maintained schema is
required.
Request encoding¶
POST bodies for discovery/settings, enrichment credentials, recommendation, and playlist writes
are form fields, not JSON. ID lists in forms are comma-separated strings. GET parameters
are query parameters. POST /sonic/refresh also takes query parameters; job/import actions
take no body. All paths below are relative to the JSON base URL.
Read-only examples for the normal product server:
curl http://127.0.0.1:8789/api/library/status
curl 'http://127.0.0.1:8789/api/recommend/typeahead?q=radiohead'
curl -X POST http://127.0.0.1:8789/api/recommend \
--data-urlencode 'seed_ids=123' --data 'limit=20' --data 'method=average'
Replace 123 with a local track ID from typeahead. POST /recommend only previews; its response
contains seed_track_ids, method, recommendations with track_id, per-signal score and
availability, sources, sonic_coverage, and effective weights. This is a different shape
from the nested service/MCP DTOs (recommendations[].track.id).
Operations¶
| Area | Routes | Behavior |
|---|---|---|
| Discovery | GET /discovery, POST /discovery/check, GET /discovery/plex-servers |
Probe local state/Plex; check accepts secret overrides in the body |
| Configuration | POST /discovery/config, POST /discovery/init-db |
Save settings only, or save and initialize the local database |
| Library | GET /library/status, POST /library/import |
Status or submit an incremental import job |
| Enrichment | POST /enrichment/listenbrainz, POST /enrichment/spotify |
Save supplied credentials and submit enrichment jobs |
| Recommendations | GET /recommend/presets, GET /recommend/typeahead, POST /recommend |
Presets, seed search, and read-only recommendations |
| Playlists | GET /playlists, POST /playlists/create |
List Plex audio playlists or write an approved selection |
| Population | GET /playlists/{playlist_id}/preview, POST /playlists/{playlist_id}/populate |
Preview complements or append approved IDs |
| Sonic | GET /sonic/status, POST /sonic/refresh, POST /sonic/import |
Plex coverage, trigger/watch analysis, or submit local vector import |
| Dashboard | GET /dashboard |
Snapshot; check_server=true also probes Plex |
| Jobs | GET /jobs/{job_id}, POST /jobs/{job_id}/cancel, DELETE /jobs/{job_id} |
Poll, request cooperative cancellation, or delete terminal history |
Import, enrichment, and sonic import return {"job_id": ...}; poll the job route for progress.
One persisted writer claim per database prevents overlapping managed jobs. Pending and
cancel-requested jobs keep the claim until the worker finishes. Settings changes are rejected
while a managed writer is active. See recovery.
POST /sonic/refresh is synchronous and triggers Plex's whole pending MusicAnalysis backlog.
Its days parameter scopes watching/reporting, not the remote work.
Playlist approval and retries¶
POST /playlists/createrequiresname,seed_ids, and approvedtrack_ids. Seeds come first; both ID lists must be nonempty positive SQLite integers.POST /playlists/{playlist_id}/populaterequires a nonempty approvedtrack_idslist.playlist_idis Plex's string rating key, not a local track ID or playlist title.- Writes preserve approved order, deduplicate IDs, and reject stale/unmapped selections before any Plex write. Weights and filters belong to preview requests. Approval is enforced by the client workflow; routes do not store a preview or verify an approval token.
- Create retries reuse a playlist only when its name and ordered contents match exactly.
A name collision with different contents raises
PlexAPIError(HTTP 502). - Populate skips tracks already in Plex. The HTTP response currently reports
added_count; core/MCP also exposealready_present_count.
Seed recommendation defaults to 50 results and accepts per_seed_limit and min_score.
HTTP playlist preview defaults to 40 results and exposes method, year filters, artist cap, and
six weights; it does not expose per_seed_limit or min_score. Core/MCP populate previews
default to 10 and expose both. Consult the generated signatures for surface-specific defaults.
Error contract¶
| Exception or validation | HTTP status |
|---|---|
NotFoundError |
404 |
ConfigurationError |
400 |
JobConflictError |
409 |
PlexAPIError |
502 |
Other MusicSeedError |
500 |
| FastAPI request validation (such as missing required fields) | 422 |
Typed errors return {"detail": "..."}; FastAPI validation details are structured lists.
Playlist routes explicitly reject malformed approved-ID lists and invalid methods with 400.
Some optional numeric fields are strings converted inside routes; malformed values are not all
covered by FastAPI's 422 validation. These mappings do not promise that every invalid request
currently receives a structured validation response.