5.4 KiB
Blastoise API Reference
Blastoise is a synchronized music server. The server owns channel time and queues; clients play audio locally.
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
Golden Rule
Use track.id for every machine operation:
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
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
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:
{ "username": "test", "password": "testuser" }
{ "name": "Channel or playlist name", "description": "optional" }
{ "mode": "repeat-all" }
{ "index": 3 }
{ "timestamp": 45.5 }
Queue and playlist track mutation:
{ "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:
Range: bytes=0-999999
WebSocket
Connect to:
ws://mhsgroove.peterino.com:3001/api/channels/:channelId/ws
Client messages:
{ "action": "switch", "channelId": "abc123" }
{ "action": "pause" }
{ "action": "unpause" }
{ "action": "seek", "timestamp": 45.5 }
{ "action": "jump", "index": 3 }
Server messages:
{ "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:
- Store the state and
performance.now(). - If
state.queueexists, replace the local queue cache. - If
state.trackis null, pause and clear the player. - If
state.track.idchanged, setaudio.srcto/api/tracks/:trackIdand seek tostate.currentTimestamp. - If same track and drift is
>= 2s, seek tostate.currentTimestamp. - If
state.paused, pause. Otherwise callaudio.play(). - 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/:iddoes not include the queue. WebSocket connect does.POST /api/playlists/shared/:tokencopies a playlist; there is no/copy.- Cache by
track.id, never by filename. - The server does not decode audio. Clients are synchronized local players.