Tape Music Hub API Reference for Agents
Current REST + SSE API for automation against the desktop hub.
Defaults
- Base URL:
http://localhost:38472 - Protocol: REST + SSE
- Format: JSON
- Data dir:
~/.music-hub
Port
The installed Desktop app always serves on 38472 — it is not user-configurable
from the app UI, so a local agent can hardcode it.
PORT is only configurable for Docker/server deployments, set at container
start via the PORT environment variable — the Docker guide
itself always uses the 38472 default. An agent targeting a Docker-deployed
hub should get the actual port from whoever deployed it (their
docker-compose/env config), not assume 38472.
Trust and auth
- Loopback / same-machine: no auth required, full API access.
- LAN (same Wi-Fi): most read endpoints open; writes may require pairing or
MUSIC_HUB_API_TOKEN. - Remote / proxied: requires active license + bearer token from
/api/auth/pairor/api/auth/refresh.
Public unauthenticated routes:
GET /api/ping,GET /api/infoPOST /api/auth/pair,POST /api/auth/refreshGET /api/share/:type/:id/bootstrapPOST /api/share/session/heartbeatGET /api/collab/invites/preview-opaque,POST /api/collab/invites/claim-opaque
Fast start
# Health + info
curl http://localhost:38472/api/ping
curl http://localhost:38472/api/infoIf databaseHealth.status is not healthy, most library routes return 503 database_unavailable.
Pairing flow (for remote agents)
1. Arm pairing window (from the desktop UI)
Open Connections (sidebar) in the desktop app and arm a pairing window. Agents on the same machine can read pairing state directly.
2. Exchange pairing code for tokens
curl -X POST http://localhost:38472/api/auth/pair \
-H 'Content-Type: application/json' \
-d '{"pairingCode":"abc123","deviceId":"agent-1","deviceName":"automation"}'3. Use the access token
curl "http://localhost:38472/api/tracks?limit=5" \
-H "Authorization: Bearer $ACCESS_TOKEN"4. Refresh when needed
curl -X POST http://localhost:38472/api/auth/refresh \
-H 'Content-Type: application/json' \
-d '{"deviceId":"agent-1","refreshToken":"..."}'Core read endpoints
Library & Stats
GET /api/library/stats— track/album/artist counts, total duration/size, distributionsGET /api/library/filters— available genres, tags, formats, BPM range, year range, foldersGET /api/tracks?limit=50&offset=0— paginated tracks with filters (genre, year, BPM, tags, folder, duration, sort)GET /api/tracks?match=query— full-text filter across title, artist, album, genres, tagsGET /api/tracks/:id— single trackGET /api/tracks/:id/metadata— metadata overlay (genres, tags, enrichment, ISRC, barcode, MBIDs)
Core track fields (GET /api/tracks/:id, and the tracks array from list
endpoints):
| Field | Type | Description |
|---|---|---|
id | string | Unique track ID |
title | string | Track title |
artist | string | Primary artist |
album | string | Album name |
albumArtist | string? | Album artist |
year | number? | Release year |
genre | string? | Genre tag |
trackNumber | number? | Track position in album |
duration | number | Duration in seconds |
bpm | number? | Beats per minute |
bitrate | number? | Bitrate in kbps |
format | string | File format (mp3, flac, etc.) |
filePath | string | Absolute path to file |
lastPlayed | string? | ISO 8601 timestamp |
dateAdded | string | ISO 8601 timestamp |
This is the core field set only. GET /api/tracks/:id/metadata layers
additional fields on top (genres[], tags[], enrichment provenance, ISRC,
barcode, MBIDs) — call it directly rather than assuming its shape from this
table. Albums, artists, playlists and every other resource below do not have
a published field table yet: call the endpoint against a running hub and read
the live response.
Library structure & folders
GET /browse— root level returns the configured source folders (the roots added in Settings > Folders); passpath=<folder>to walk into a folder and see its entries.GET /api/library/filters— includes the distinctfoldersvalues actually present across indexed tracks, alongside genres, tags, formats, BPM range and year range. Use this to see what folder structure Tape has indexed today, not just what roots are configured.
There is no vector/embedding index in Tape's database. All metadata is relational (title, artist, album, genre, BPM, tags, enrichment fields, etc.) — reason over those structured fields, not over similarity vectors.
There is currently no endpoint to move or rename files on disk. Folder/file reorganization stays host-only; an agent can read structure and metadata and propose field-level metadata changes (see Core write endpoints), but not trigger a physical reorganization yet.
Albums, Artists, Labels
GET /api/albums?limit=50&offset=0GET /api/albums/:id,GET /api/albums/:id/tracksGET /api/artists?limit=50&offset=0GET /api/artists/:id,GET /api/artists/:id/tracks,GET /api/artists/:id/albumsGET /api/labels?limit=50&offset=0,GET /api/labels/:id
Playlists
GET /api/playlistsGET /api/playlists/:id,GET /api/playlists/:id/tracksPOST /api/playlists{ "name": "...", "description": "...", "folder": "..." }POST /api/playlists/:id/tracks{ "trackIds": [...] }DELETE /api/playlists/:id
Search
GET /api/search?q=miles+davis&type=tracks|albums|artists&limit=50
Playback & History
GET /api/playback/state— current queue stateGET /api/library/recently-played?limit=10GET /api/analytics/play-history?days=7&limit=100— chronological play eventsGET /api/analytics/hub-counter— global playback counter + license gate
Agent Playback
POST /api/agent/play— start playback from agent context{ "trackIds": ["abc123"], "startIndex": 0 }POST /api/agent/queue— add tracks to current queue without interrupting{ "trackIds": ["ghi789"] }
Shared / Saved
GET /api/saved— saved track/artist/album IDsGET /api/library/bookmarked— bookmarked playlistsGET /api/share/:type/:id/bootstrap— public share page data
AI Context
GET /api/ai/context— current track, queue, session snapshot
Core write endpoints
PUT /api/saved/tracks/:id{ "status": "new|saved|heard", "source": "desktop|mobile" }POST /api/play/events{ "trackId": "...", "source": "desktop|mobile", "completed": true }PUT /api/tracks/:id/metadata/curated{ "genres": [...], "tags": [...], "styles": [...] }PUT /api/tracks/:id/metadata/overrides{ "manualGenres": [...], "manualTags": [...] }DELETE /api/tracks/:id/metadata/overridesPOST /api/tracks/:id/tags/write-to-filePUT /api/tracks/:id/primary-metadata{ "title": "...", "artist": "...", "album": "..." }
Enrichment
GET /api/tracks/:id/enrichment/job— current enrichment statusPOST /api/tracks/:id/re-enrich/search{ "provider": "musicbrainz|discogs", "title": "...", "artist": "...", "album": "..." }POST /api/tracks/:id/re-enrich/apply{ "provider": "musicbrainz", "candidate": {...} }
License
POST /api/license/activate{ "licenseKey": "..." }POST /api/license/validate{ "force": true }GET /api/license/status
Inbox / Library triage
POST /api/library/inbox/remove{ "trackIds": [...] }POST /api/library/inbox/clearPOST /api/library/inbox/mark-heard{ "trackIds": [...] }POST /api/library/inbox/mark-to-listen{ "trackIds": [...] }POST /api/library/inbox/undo
SSE feeds
GET /api/library/events— track updates, enrichment status, metadata changesGET /api/events/stream— scan progress, track added/updated/deleted
Remote / Tunnel
GET /api/remote/statusPOST /api/remote/cloudflare/setup,POST /api/remote/cloudflare/deletePOST /api/remote/route/toggle{ "route": "cloudflare|sovereign", "enabled": true }
Legacy Tailscale setup endpoints may still exist in older builds; they are not part of the 0.3.0 Settings product path. Prefer Personal Private Network (Iroh/sovereign) + optional Cloudflare.
Diagnostics
GET /api/diagnostics/statusPOST /api/diagnostics/upload
Settings
GET /api/settingsPUT /api/settings/server{ "name": "...", "port": 38472 }
Local-only operations
These must run on the desktop host itself (loopback):
POST /api/settings/folders/rescanPOST /api/database/rebuildGET /api/auth/devices,DELETE /api/auth/devices/:deviceIdGET /api/analytics/hub-counter/recalculate
Share-specific public routes
GET /api/share/playlist/:id/bootstrapGET /api/share/artist/:id/bootstrapGET /api/share/album/:id/bootstrapGET /api/share/track/:id/bootstrapPOST /api/share/session/heartbeat
Bootstrap responses include shareSessionId and mediaToken. Both are short-lived.
Recovery
If the hub reports a faulted database:
curl http://localhost:38472/api/info
# Check databaseHealth.status
curl -X POST http://localhost:38472/api/database/rebuildDo not script destructive DB deletion. The hub preserves and archives faulted DBs.
Failure modes
401— remote auth missing or expired403— license required or local-only endpoint called remotely404— resource not found409 pairing_not_armed— pairing window not active503 database_unavailable— hub DB is faulted or incompatible
Full reference
See the complete markdown doc shipped with the desktop app:
desktop/docs/API-REFERENCE-FOR-AGENTS.md
Keeping this reference accurate
This page is hand-written prose, not generated from an OpenAPI/JSON Schema
spec — it can drift from desktop/src/core/api/routes/*.ts the same way the
old "Suggestions" section did (documented /api/suggestions and
/api/tracks/:id/similar, neither of which had a matching route). Follow this
pattern when routes change:
- Only document what exists. A route must already be implemented in
desktop/src/core/api/routes/*.tsbefore it's added here or todesktop/docs/API-REFERENCE-FOR-AGENTS.mdor Integration Guide. Planned/future endpoints belong inworking/roadmap/orworking/tasks/, never in agent-facing docs — that's how this page andworking/API-REFERENCE-FOR-AGENTS.mdended up describing a/mcpserver and/api/ai/suggest//api/ai/patternsthat were never built. - Update in the same change. A PR that adds, renames or removes a route
under
desktop/src/core/api/routes/updates this page (andintegration.mdxwhen it affects an agent workflow) in that same change, not as a follow-up. - Audit periodically. List the routes actually mounted and diff by eye
against what's documented here:
Anything in that output missing from this page, or documented here with no matching route, is the audit finding.
grep -rhoE "router\.(get|post|put|delete)\('[^']+'" desktop/src/core/api/routes/*.ts | sort -u