# Furumi Web Player API Base URL: `http://:/api` All endpoints require authentication when `--token` is set (via cookie `furumi_token=` or query param `?token=`). All entity references use **slugs** — 12-character hex identifiers (not sequential IDs). ## Artists ### `GET /api/artists` List all artists that have at least one track. **Response:** ```json [ { "slug": "a1b2c3d4e5f6", "name": "Pink Floyd", "album_count": 5, "track_count": 42 } ] ``` Sorted alphabetically by name. ### `GET /api/artists/:slug` Get artist details. **Response:** ```json { "slug": "a1b2c3d4e5f6", "name": "Pink Floyd" } ``` **Errors:** `404` if not found. ### `GET /api/artists/:slug/albums` List all albums by an artist. **Response:** ```json [ { "slug": "b2c3d4e5f6a7", "name": "Wish You Were Here", "year": 1975, "track_count": 5, "has_cover": true } ] ``` Sorted by year (nulls last), then name. ### `GET /api/artists/:slug/tracks` List all tracks by an artist across all albums. **Response:** same as album tracks (see below). Sorted by album year, album name, track number, title. ## Albums ### `GET /api/albums/:slug` List all tracks in an album. **Response:** ```json [ { "slug": "c3d4e5f6a7b8", "title": "Have a Cigar", "track_number": 3, "duration_secs": 312.5, "artist_name": "Pink Floyd", "album_name": "Wish You Were Here", "album_slug": "b2c3d4e5f6a7", "genre": "Progressive Rock" } ] ``` Sorted by track number (nulls last), then title. Fields `album_name`, `album_slug` may be `null` for tracks without an album. ### `GET /api/albums/:slug/cover` Serve the album cover image from the `album_images` table. **Response:** Binary image data with appropriate `Content-Type` (`image/jpeg`, `image/png`, etc.) and `Cache-Control: public, max-age=86400`. **Errors:** `404` if no cover exists. ## Tracks ### `GET /api/tracks/:slug` Get full track details. **Response:** ```json { "slug": "c3d4e5f6a7b8", "title": "Have a Cigar", "track_number": 3, "duration_secs": 312.5, "genre": "Progressive Rock", "storage_path": "/music/storage/Pink Floyd/Wish You Were Here/03 - Have a Cigar.flac", "artist_name": "Pink Floyd", "artist_slug": "a1b2c3d4e5f6", "album_name": "Wish You Were Here", "album_slug": "b2c3d4e5f6a7", "album_year": 1975 } ``` **Errors:** `404` if not found. ### `GET /api/tracks/:slug/cover` Serve cover art for a specific track. Resolution order: 1. Album cover from `album_images` table (if the track belongs to an album with a cover) 2. Embedded cover art extracted from the audio file metadata (ID3/Vorbis/etc. via Symphonia) 3. `404` if no cover art is available **Response:** Binary image data with `Content-Type` and `Cache-Control: public, max-age=86400`. **Errors:** `404` if no cover art found. ## Streaming ### `GET /api/stream/:slug` Stream the audio file for a track. Supports HTTP **Range requests** for seeking: - Full response: `200 OK` with `Content-Length` and `Accept-Ranges: bytes` - Partial response: `206 Partial Content` with `Content-Range` - Invalid range: `416 Range Not Satisfiable` `Content-Type` is determined by the file extension (e.g. `audio/flac`, `audio/mpeg`). **Errors:** `404` if track or file not found. ## Search ### `GET /api/search?q=&limit=` Search across artists, albums, and tracks by name (case-insensitive substring match). | Parameter | Required | Default | Description | |-----------|----------|---------|-------------| | `q` | yes | — | Search query | | `limit` | no | 20 | Max results | **Response:** ```json [ { "result_type": "artist", "slug": "a1b2c3d4e5f6", "name": "Pink Floyd", "detail": null }, { "result_type": "album", "slug": "b2c3d4e5f6a7", "name": "Wish You Were Here", "detail": "Pink Floyd" }, { "result_type": "track", "slug": "c3d4e5f6a7b8", "name": "Have a Cigar", "detail": "Pink Floyd" } ] ``` `detail` contains the artist name for albums and tracks, `null` for artists. Sorted by result type (artist → album → track), then by name. ## Authentication When `--token` / `FURUMI_PLAYER_TOKEN` is set: - **Cookie:** `furumi_token=` — set after login - **Query parameter:** `?token=` — redirects to player and sets cookie When token is empty, authentication is disabled and all endpoints are public. Unauthenticated requests receive `401 Unauthorized` with a login form. ## Error format All errors return JSON: ```json { "error": "description of the error" } ``` With appropriate HTTP status code (`400`, `404`, `500`, etc.).