- gRPC service with MusicBrainz provider - PostgreSQL schema with migrations - Service layer with database-first caching - Repository pattern for data access - YAML configuration support - Research documentation for 17 music metadata projects
22 KiB
Meelo API Reference
API Overview
Meelo exposes REST APIs for all services. The primary API is the Server (NestJS) at port 4000, accessed via /api/* through Nginx. Scanner and Matcher have smaller APIs for administrative tasks.
Base URL: http://localhost/api (via Nginx) or http://localhost:4000 (direct)
Authentication: JWT Bearer tokens or API keys
Content Type: application/json
Documentation: Swagger UI at /api/docs
Authentication
JWT Authentication
Most endpoints require JWT authentication. Obtain a token via login:
POST /api/auth/login
{
"username": "admin",
"password": "password"
}
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": 1,
"username": "admin",
"isAdmin": true
}
}
Include token in subsequent requests:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
API Key Authentication
Scanner and Matcher use API keys for internal communication:
x-api-key: <key from .env>
Anonymous Access
If ALLOW_ANONYMOUS=1 in .env, authentication is optional. All endpoints accessible without tokens.
Auth Endpoints
Register User
POST /api/auth/register
Create new user account.
Request:
{
"username": "newuser",
"password": "securepassword"
}
Response:
{
"id": 2,
"username": "newuser",
"isAdmin": false
}
First registered user becomes admin automatically.
Login
POST /api/auth/login
Authenticate and receive JWT token.
Request:
{
"username": "admin",
"password": "password"
}
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": 1,
"username": "admin",
"isAdmin": true
}
}
Get Current User
GET /api/auth/me
Retrieve authenticated user details.
Response:
{
"id": 1,
"username": "admin",
"isAdmin": true,
"createdAt": "2024-01-15T10:30:00Z"
}
Artist Endpoints
List Artists
GET /api/artists
Retrieve paginated artist list.
Query Parameters:
skip(number): Offset for pagination (default: 0)take(number): Limit results (default: 20, max: 100)sortBy(string): Sort field (name, createdAt)sortOrder(string): asc or desc
Response:
{
"items": [
{
"id": 1,
"name": "The Beatles",
"slug": "the-beatles",
"sortName": "Beatles, The",
"illustration": {
"url": "/illustrations/artist-1.jpg",
"blurhash": "LKO2?U%2Tw=w]~RBVZRi};RPxuwH",
"colors": ["#1a1a1a", "#f0f0f0"]
},
"albumCount": 13,
"songCount": 213
}
],
"total": 150,
"skip": 0,
"take": 20
}
Get Artist
GET /api/artists/:id
Retrieve artist details with relationships.
Query Parameters:
include(string[]): Related entities to include (albums, songs, videos, areas)
Response:
{
"id": 1,
"name": "The Beatles",
"slug": "the-beatles",
"sortName": "Beatles, The",
"illustration": { ... },
"externalMetadata": {
"description": "The Beatles were an English rock band...",
"rating": 95,
"sources": [
{
"provider": "wikipedia",
"url": "https://en.wikipedia.org/wiki/The_Beatles"
}
]
},
"areas": [
{
"id": 1,
"name": "Liverpool",
"type": "city",
"iso3166": "GB-LIV"
}
],
"albums": [ ... ],
"songs": [ ... ],
"videos": [ ... ]
}
Create Artist
POST /api/artists
Create new artist. Admin only.
Request:
{
"name": "New Artist",
"sortName": "Artist, New",
"areaIds": [1, 2]
}
Response:
{
"id": 151,
"name": "New Artist",
"slug": "new-artist",
"sortName": "Artist, New"
}
Update Artist
PATCH /api/artists/:id
Update artist details. Admin only.
Request:
{
"name": "Updated Name",
"sortName": "Name, Updated"
}
Delete Artist
DELETE /api/artists/:id
Delete artist and cascade to albums/songs. Admin only.
Album Endpoints
List Albums
GET /api/albums
Retrieve paginated album list.
Query Parameters:
skip,take,sortBy,sortOrder(same as artists)type(string): Filter by album type (studio, live, compilation, etc.)artistId(number): Filter by artistgenreId(number): Filter by genreyear(number): Filter by release year
Response:
{
"items": [
{
"id": 1,
"name": "Abbey Road",
"slug": "abbey-road",
"type": "studio",
"releaseDate": "1969-09-26",
"illustration": { ... },
"artist": {
"id": 1,
"name": "The Beatles"
},
"trackCount": 17,
"duration": 2832
}
],
"total": 500,
"skip": 0,
"take": 20
}
Get Album
GET /api/albums/:id
Retrieve album details with tracks and releases.
Query Parameters:
include(string[]): Related entities (tracks, releases, genres, labels)
Response:
{
"id": 1,
"name": "Abbey Road",
"slug": "abbey-road",
"type": "studio",
"releaseDate": "1969-09-26",
"illustration": { ... },
"artist": { ... },
"genres": [
{ "id": 1, "name": "Rock" }
],
"releases": [
{
"id": 1,
"name": "Abbey Road (Original)",
"releaseDate": "1969-09-26",
"trackCount": 17
},
{
"id": 2,
"name": "Abbey Road (2019 Remaster)",
"releaseDate": "2019-09-27",
"trackCount": 17
}
],
"tracks": [ ... ],
"externalMetadata": { ... }
}
Album Types
Enum values for type field:
studio: Studio albumlive: Live albumcompilation: Compilationsoundtrack: Soundtrackep: Extended playsingle: Singleremix: Remix albumbootleg: Bootleginterview: Interviewother: Other
Song Endpoints
List Songs
GET /api/songs
Retrieve paginated song list.
Query Parameters:
skip,take,sortBy,sortOrderartistId(number): Filter by artistalbumId(number): Filter by albumgenreId(number): Filter by genretype(string): Filter by song type
Response:
{
"items": [
{
"id": 1,
"name": "Come Together",
"slug": "come-together",
"type": "original",
"artist": {
"id": 1,
"name": "The Beatles"
},
"featuring": [],
"trackCount": 3,
"duration": 259
}
],
"total": 2000,
"skip": 0,
"take": 20
}
Get Song
GET /api/songs/:id
Retrieve song details with tracks and lyrics.
Query Parameters:
include(string[]): Related entities (tracks, lyrics, videos)
Response:
{
"id": 1,
"name": "Come Together",
"slug": "come-together",
"type": "original",
"artist": { ... },
"featuring": [],
"bpm": 82,
"lyrics": {
"plain": "Here come old flat top...",
"synced": [
{ "time": 0, "text": "Here come old flat top" },
{ "time": 3500, "text": "He come grooving up slowly" }
]
},
"tracks": [
{
"id": 1,
"name": "Come Together",
"type": "audio",
"duration": 259,
"bitrate": 320000,
"release": {
"id": 1,
"name": "Abbey Road (Original)"
}
}
],
"externalMetadata": { ... }
}
Song Types
Enum values for type field:
original: Original recordinglive: Live performanceacoustic: Acoustic versionremix: Remixcover: Cover versiondemo: Demo recordinginstrumental: Instrumentalkaraoke: Karaoke versionradio_edit: Radio editextended: Extended versionclean: Clean versionexplicit: Explicit version
Track Endpoints
Get Track
GET /api/tracks/:id
Retrieve track details.
Response:
{
"id": 1,
"name": "Come Together",
"type": "audio",
"duration": 259,
"bitrate": 320000,
"codec": "mp3",
"ripSource": "cd",
"isBonus": false,
"isRemastered": false,
"discIndex": 1,
"trackIndex": 1,
"song": { ... },
"release": { ... },
"sourceFile": {
"id": 1,
"path": "/music/The Beatles/Abbey Road/01 Come Together.mp3",
"checksum": "abc123...",
"fingerprint": "AQADtE..."
}
}
Stream Track
GET /api/tracks/:id/stream
Stream audio file. Returns audio data with appropriate Content-Type.
Query Parameters:
transcode(boolean): Enable transcoding (default: false)bitrate(number): Target bitrate for transcoding (128, 192, 256, 320)
Response: Audio stream (audio/mpeg, audio/flac, etc.)
Rip Source Types
Enum values for ripSource field:
cd: CD ripvinyl: Vinyl ripweb: Web downloadstream: Stream ripother: Other source
Release Endpoints
Get Release
GET /api/releases/:id
Retrieve release details with tracks.
Response:
{
"id": 1,
"name": "Abbey Road (Original)",
"releaseDate": "1969-09-26",
"album": { ... },
"label": {
"id": 1,
"name": "Apple Records"
},
"tracks": [ ... ],
"extensions": {
"catalogNumber": "PCS 7088",
"barcode": "5099969945724"
}
}
Library Endpoints
List Libraries
GET /api/libraries
Retrieve all configured libraries.
Response:
{
"items": [
{
"id": 1,
"name": "Main Library",
"path": "/music",
"fileCount": 5000,
"lastScan": "2024-01-20T15:30:00Z"
}
]
}
Create Library
POST /api/libraries
Create new library. Admin only.
Request:
{
"name": "Jazz Collection",
"path": "/music/jazz"
}
Update Library
PATCH /api/libraries/:id
Update library settings. Admin only.
Delete Library
DELETE /api/libraries/:id
Delete library and all associated files. Admin only.
File Endpoints
List Files
GET /api/files
Retrieve files with filters.
Query Parameters:
libraryId(number): Filter by librarytrackId(number): Filter by trackorphaned(boolean): Show files without tracks
Response:
{
"items": [
{
"id": 1,
"path": "/music/The Beatles/Abbey Road/01 Come Together.mp3",
"checksum": "abc123...",
"fingerprint": "AQADtE...",
"library": { ... },
"track": { ... }
}
]
}
Register File
POST /api/files
Register new file. Used by Scanner.
Request:
{
"path": "/music/artist/album/track.mp3",
"checksum": "abc123...",
"fingerprint": "AQADtE...",
"libraryId": 1,
"metadata": {
"title": "Track Title",
"artist": "Artist Name",
"album": "Album Name",
"duration": 259,
"bitrate": 320000
}
}
Genre Endpoints
List Genres
GET /api/genres
Retrieve all genres.
Response:
{
"items": [
{
"id": 1,
"name": "Rock",
"albumCount": 150,
"songCount": 2000
}
]
}
Get Genre
GET /api/genres/:id
Retrieve genre with albums and songs.
Playlist Endpoints
List Playlists
GET /api/playlists
Retrieve user's playlists.
Query Parameters:
public(boolean): Filter by public/private
Response:
{
"items": [
{
"id": 1,
"name": "Favorites",
"slug": "favorites",
"isPublic": false,
"allowChanges": false,
"entryCount": 50,
"owner": {
"id": 1,
"username": "admin"
}
}
]
}
Get Playlist
GET /api/playlists/:id
Retrieve playlist with entries.
Response:
{
"id": 1,
"name": "Favorites",
"slug": "favorites",
"isPublic": false,
"allowChanges": false,
"owner": { ... },
"entries": [
{
"id": 1,
"index": 0,
"track": { ... }
}
]
}
Create Playlist
POST /api/playlists
Create new playlist.
Request:
{
"name": "New Playlist",
"isPublic": false,
"allowChanges": false
}
Update Playlist
PATCH /api/playlists/:id
Update playlist details. Owner only.
Delete Playlist
DELETE /api/playlists/:id
Delete playlist. Owner only.
Add Track to Playlist
POST /api/playlists/:id/entries
Add track to playlist.
Request:
{
"trackId": 1,
"index": 0
}
Remove Track from Playlist
DELETE /api/playlists/:id/entries/:entryId
Remove track from playlist.
Reorder Playlist
PATCH /api/playlists/:id/entries/reorder
Reorder playlist entries.
Request:
{
"entryId": 1,
"newIndex": 5
}
Search Endpoints
Global Search
GET /api/search
Search across artists, albums, songs, videos.
Query Parameters:
q(string): Search query (required)limit(number): Max results per type (default: 10)types(string[]): Filter by types (artist, album, song, video)
Response:
{
"artists": [
{
"id": 1,
"name": "The Beatles",
"slug": "the-beatles",
"illustration": { ... }
}
],
"albums": [ ... ],
"songs": [ ... ],
"videos": [ ... ]
}
Reindex Search
POST /api/search/reindex
Rebuild MeiliSearch index from database. Admin only.
External Metadata Endpoints
Get External Metadata
GET /api/external-metadata/:entityType/:entityId
Retrieve external metadata for entity.
Path Parameters:
entityType(string): artist, album, songentityId(number): Entity ID
Response:
{
"description": "The Beatles were an English rock band...",
"rating": 95,
"sources": [
{
"provider": "wikipedia",
"url": "https://en.wikipedia.org/wiki/The_Beatles"
},
{
"provider": "musicbrainz",
"url": "https://musicbrainz.org/artist/..."
}
]
}
Update External Metadata
POST /api/external-metadata/:entityType/:entityId
Push external metadata. Used by Matcher.
Request:
{
"description": "Artist biography...",
"rating": 85,
"sources": [
{
"provider": "wikipedia",
"url": "https://..."
}
]
}
Scrobbler Endpoints
List Scrobblers
GET /api/scrobblers
Retrieve user's connected scrobblers.
Response:
{
"items": [
{
"id": 1,
"provider": "lastfm",
"username": "lastfm_user",
"connectedAt": "2024-01-15T10:30:00Z"
}
]
}
Authorize Last.fm
GET /api/scrobblers/lastfm/authorize
Redirect to Last.fm OAuth page.
Last.fm Callback
GET /api/scrobblers/lastfm/callback
OAuth callback. Exchanges code for token.
Query Parameters:
token(string): OAuth token from Last.fm
Authorize ListenBrainz
POST /api/scrobblers/listenbrainz/authorize
Connect ListenBrainz account.
Request:
{
"token": "user_token_from_listenbrainz"
}
Remove Scrobbler
DELETE /api/scrobblers/:id
Disconnect scrobbler.
Scrobble Track
POST /api/scrobblers/scrobble
Scrobble track play. Called automatically by Front.
Request:
{
"trackId": 1,
"timestamp": 1705320600
}
Video Endpoints
List Videos
GET /api/videos
Retrieve music videos.
Query Parameters:
skip,take,sortBy,sortOrderartistId(number): Filter by artistsongId(number): Filter by songtype(string): Filter by video type
Response:
{
"items": [
{
"id": 1,
"name": "Come Together (Official Video)",
"slug": "come-together-official-video",
"type": "official",
"duration": 259,
"song": { ... },
"sourceFile": { ... }
}
]
}
Get Video
GET /api/videos/:id
Retrieve video details.
Stream Video
GET /api/videos/:id/stream
Stream video file. Proxies to Kyoo transcoder.
Query Parameters:
resolution(string): Target resolution (480p, 720p, 1080p)
Response: HLS manifest or video stream
Video Types
Enum values for type field:
official: Official music videolive: Live performancelyric: Lyric videoaudio: Audio-only videobehind_the_scenes: Behind the scenesinterview: Interviewdocumentary: Documentaryfan_made: Fan-made videoother: Other
Illustration Endpoints
Get Illustration
GET /api/illustrations/:id
Retrieve illustration image.
Response: Image file (image/jpeg, image/png)
Upload Illustration
POST /api/illustrations
Upload illustration for entity. Admin only.
Request: multipart/form-data with image file
Response:
{
"id": 1,
"url": "/illustrations/1.jpg",
"blurhash": "LKO2?U%2Tw=w]~RBVZRi};RPxuwH",
"colors": ["#1a1a1a", "#f0f0f0"],
"aspectRatio": 1.0
}
Area Endpoints
List Areas
GET /api/areas
Retrieve geographic areas.
Query Parameters:
type(string): Filter by area type (country, city, region, etc.)parentId(number): Filter by parent area
Response:
{
"items": [
{
"id": 1,
"name": "United Kingdom",
"type": "country",
"iso3166": "GB",
"parent": null,
"children": [ ... ]
}
]
}
Area Types
Enum values for type field:
country: Countrysubdivision: State/provincecounty: Countymunicipality: City/towncity: Citydistrict: Districtisland: Island
User Endpoints
List Users
GET /api/users
Retrieve all users. Admin only.
Response:
{
"items": [
{
"id": 1,
"username": "admin",
"isAdmin": true,
"createdAt": "2024-01-15T10:30:00Z"
}
]
}
Update User
PATCH /api/users/:id
Update user details. Admin or self only.
Request:
{
"password": "newpassword"
}
Delete User
DELETE /api/users/:id
Delete user. Admin only.
Scanner API
Scanner exposes administrative endpoints at port 8133 (via /scanner/ through Nginx).
Health Check
GET /scanner/
Check scanner status.
Response:
{
"status": "healthy",
"activeTasks": 0,
"lastScan": "2024-01-20T15:30:00Z"
}
List Tasks
GET /scanner/tasks
Retrieve active scan tasks.
Response:
{
"tasks": [
{
"id": "task-1",
"libraryId": 1,
"status": "running",
"progress": 0.45,
"filesProcessed": 2250,
"totalFiles": 5000
}
]
}
Scan Library
POST /scanner/scan/:libraryId
Trigger full library scan.
Response:
{
"taskId": "task-1",
"status": "started"
}
Scan All Libraries
POST /scanner/scan
Trigger scan for all libraries.
Clean Orphans
POST /scanner/clean
Remove database entries for deleted files.
Response:
{
"removedFiles": 15,
"removedTracks": 10
}
Refresh Metadata
POST /scanner/refresh
Re-query providers for existing tracks.
Response:
{
"taskId": "task-2",
"status": "started"
}
Matcher API
Matcher exposes minimal API at port 6789 (via /matcher/ through Nginx).
Health Check
GET /matcher/health
Check matcher status.
Response:
{
"status": "healthy",
"queueLength": 5,
"activeProviders": ["musicbrainz", "genius", "wikipedia", "lrclib"]
}
Error Responses
All endpoints return consistent error format:
{
"statusCode": 404,
"message": "Artist not found",
"error": "Not Found"
}
Common status codes:
400: Bad Request (invalid input)401: Unauthorized (missing/invalid token)403: Forbidden (insufficient permissions)404: Not Found (entity doesn't exist)409: Conflict (duplicate entity)500: Internal Server Error
Rate Limiting
No built-in rate limiting. Recommended to use Nginx rate limiting for public instances.
Pagination
List endpoints support pagination via skip and take parameters. Response includes total count:
{
"items": [ ... ],
"total": 500,
"skip": 0,
"take": 20
}
Calculate pages: totalPages = Math.ceil(total / take)
Filtering
List endpoints support filtering via query parameters. Filters are AND-combined:
GET /api/albums?artistId=1&type=studio&year=1969
Returns studio albums by artist 1 released in 1969.
Sorting
List endpoints support sorting via sortBy and sortOrder:
GET /api/artists?sortBy=name&sortOrder=asc
Default sort is usually by creation date descending.
Including Relations
Detail endpoints support include parameter to fetch related entities:
GET /api/artists/1?include=albums&include=songs
Reduces round trips for complex views.
Swagger Documentation
Interactive API documentation available at /api/docs. Includes:
- All endpoints with parameters
- Request/response schemas
- Try-it-out functionality
- Authentication setup
Useful for exploring API without reading docs.
WebSocket Events
Server emits real-time events via WebSocket at /api/events:
const socket = io('http://localhost/api/events', {
auth: { token: 'jwt_token' }
});
socket.on('track.added', (data) => {
console.log('New track:', data);
});
socket.on('scan.progress', (data) => {
console.log('Scan progress:', data.progress);
});
Event types:
track.added: New track registeredtrack.updated: Track metadata updatedscan.started: Library scan startedscan.progress: Scan progress updatescan.completed: Library scan completedmetadata.enriched: External metadata added
API Versioning
No versioning currently. Breaking changes announced in release notes. Future versions may use /api/v2/ prefix.
CORS
CORS enabled for all origins in development. Production should configure allowed origins in .env:
CORS_ORIGINS=https://meelo.example.com
Content Negotiation
All endpoints return JSON. Accept header ignored. Future versions may support XML or MessagePack.
Compression
Nginx handles gzip compression for responses >1KB. No application-level compression.
Caching
No server-side caching. Clients should implement caching via TanStack Query or similar.
Summary
Meelo's API provides comprehensive access to all music metadata and playback functionality. The REST design follows standard conventions with consistent error handling, pagination, and filtering. Swagger documentation makes exploration easy. WebSocket events enable real-time UI updates. The separation of Server, Scanner, and Matcher APIs reflects the microservices architecture while maintaining a unified interface through Nginx.