# Blastoise API Reference Blastoise is a synchronized music server. The server owns channel time and queues; clients play audio locally. ```text Reference HTTP: http://mhsgroove.peterino.com:3001 Reference WS: ws://mhsgroove.peterino.com:3001 Local HTTP: http://localhost:3001 Local WS: ws://localhost:3001 ``` Auth is an HttpOnly cookie named `blastoise_session`. Same-origin browser apps can use normal `fetch`. Separate-origin browser apps need a same-origin proxy or CORS with credentials. Native apps must store `Set-Cookie` and send it as `Cookie` on HTTP and WebSocket requests. Full details: [api-reference-full.md](./api-reference-full.md) ## Golden Rule Use `track.id` for every machine operation: ```text GET /api/tracks/:trackId ``` `track.id` is a content hash like `sha256:...`. `filename` and `title` are only for display. Queue entries, playlists, cache keys, direct links, and audio URLs should all use `track.id`. ## Core Shapes ```ts type PlaybackMode = "once" | "repeat-all" | "repeat-one" | "shuffle"; type Track = { id: string; filename: string; title: string | null; artist?: string | null; album?: string | null; duration: number; replayGainDb?: number | null; replayPeak?: number | null; available?: boolean; }; type ChannelInfo = { id: string; name: string; description: string; trackCount: number; listenerCount: number; listeners: string[]; isDefault: boolean; createdBy: number | null; }; type ChannelState = { track: Track | null; currentTimestamp: number; channelName: string; channelId: string; description: string; paused: boolean; currentIndex: number; listenerCount: number; isDefault: boolean; playbackMode: PlaybackMode; queue?: Track[]; }; type Playlist = { id: string; name: string; description: string; ownerId: number; ownerName?: string; isPublic: boolean; shareToken: string | null; trackIds: string[]; createdAt: number; updatedAt: number; }; ``` `ChannelState.queue` is optional. It appears on WebSocket connect, queue changes, and periodic refreshes. Keep the last known queue when omitted. ## Startup ```text GET /api/status GET /api/auth/me GET /api/library GET /api/channels WS /api/channels/:channelId/ws ``` Choose a channel: saved channel, else `isDefault`, else first channel. ## Endpoints | Area | Endpoints | |---|---| | Status | `GET /api/status` | | Auth | `POST /api/auth/signup`, `POST /api/auth/login`, `POST /api/auth/logout`, `GET /api/auth/me`, `POST /api/auth/kick-others` | | Channels | `GET /api/channels`, `POST /api/channels`, `GET/PATCH/DELETE /api/channels/:id` | | Playback control | `POST /api/channels/:id/jump`, `POST /api/channels/:id/seek`, `POST /api/channels/:id/mode` | | Queue | `PATCH /api/channels/:id/queue` | | Library/audio | `GET /api/library`, `GET /api/tracks/:trackId`, `POST /api/upload` | | Playlists | `GET/POST /api/playlists`, `GET/PATCH/DELETE /api/playlists/:id`, `PATCH /api/playlists/:id/tracks` | | Sharing | `POST/DELETE /api/playlists/:id/share`, `GET/POST /api/playlists/shared/:token` | | URL import | `POST /api/fetch`, `POST /api/fetch/confirm`, `GET /api/fetch`, `DELETE /api/fetch/:itemId`, `DELETE /api/fetch` | Common bodies: ```json { "username": "test", "password": "testuser" } { "name": "Channel or playlist name", "description": "optional" } { "mode": "repeat-all" } { "index": 3 } { "timestamp": 45.5 } ``` Queue and playlist track mutation: ```json { "set": ["sha256:a", "sha256:b"] } { "add": ["sha256:c"], "insertAt": 2 } { "remove": [3, 4] } { "move": [5, 6], "to": 1 } ``` Remove/move use positions, not track IDs. Duplicate tracks are allowed. Audio supports range requests: ```text Range: bytes=0-999999 ``` ## WebSocket Connect to: ```text ws://mhsgroove.peterino.com:3001/api/channels/:channelId/ws ``` Client messages: ```json { "action": "switch", "channelId": "abc123" } { "action": "pause" } { "action": "unpause" } { "action": "seek", "timestamp": 45.5 } { "action": "jump", "index": 3 } ``` Server messages: ```json { "type": "channel_list", "channels": [] } { "type": "switched", "channelId": "abc123" } { "type": "kick", "reason": "Kicked by another session" } { "type": "toast", "message": "Added: Song", "toastType": "info" } { "type": "scan_progress", "scanning": true, "processed": 1, "total": 20 } { "type": "fetch_progress", "id": "job", "status": "downloading", "progress": 50 } ``` Any message without `type` is a `ChannelState`. Guests can listen and switch channels, but cannot control playback or mutate queues. Unauthorized WebSocket control messages are ignored. ## Sync Algorithm On every `ChannelState`: 1. Store the state and `performance.now()`. 2. If `state.queue` exists, replace the local queue cache. 3. If `state.track` is null, pause and clear the player. 4. If `state.track.id` changed, set `audio.src` to `/api/tracks/:trackId` and seek to `state.currentTimestamp`. 5. If same track and drift is `>= 2s`, seek to `state.currentTimestamp`. 6. If `state.paused`, pause. Otherwise call `audio.play()`. 7. Between WebSocket updates, estimate time as `state.currentTimestamp + elapsedSeconds`, unless paused. The server is the source of truth. ## Gotchas - Some errors are JSON `{ "error": "..." }`; some are plain text. Handle both. - `GET /api/channels/:id` does not include the queue. WebSocket connect does. - `POST /api/playlists/shared/:token` copies a playlist; there is no `/copy`. - Cache by `track.id`, never by filename. - The server does not decode audio. Clients are synchronized local players.