Token
header · Authorization Authorization: Token <token>. What the token reaches depends on its
scopes — see the description at the top of this document. Lowercase
token is also accepted.
288 operations across 227 paths. The deck is the server, so anything the web UI does, a client of yours can do too.
Describes v0.120.0. Your own deck is the authority on what it runs — ask it for GET /api/openapi.yaml and you get this document as that binary
actually serves it.
Self-hosted scrobbler and listening journal.
Tapedeck ingests listens, enriches them with MusicBrainz metadata, stores them in SQLite and forwards them to Last.fm / ListenBrainz / Libre.fm. It also records how you listened — signal chains, gear, audio quality — which is the part no other scrobbler models.
| How | Reaches | |
|---|---|---|
| Session cookie | td_session, set by POST /api/v1/auth/login |
everything |
| API token | Authorization: Token <t> |
/1/*, plus whatever its scopes allow |
| Metrics bearer | Authorization: Bearer <t> |
GET /metrics only |
tokens.scopes is a space- or comma-separated set. Three grants matter, and
none of them implies another:
submit — post listens (POST /1/submit-listens). Every scrobble client
has this; it must not carry the power to delete the history it appends to.read (since v0.37) — read your own data on the endpoints whose security
lists apiToken: [read].write (since v0.39) — change your own data on the endpoints whose
security lists apiToken: [write].all grants both read and write. Matching is exact — a scope of
rewrite does not grant write.
A valid token that lacks the required scope gets 403, not 401. It authenticated fine and re-authenticating will not help; a 401 would send a client into a token-refresh loop it cannot win.
Everything under /admin/, plus your sources, your forwarding connections,
Discogs credentials, data export/backup, the sanitiser and the enrichment
jobs. Those are session-only, permanently. A leaked token is a bad
afternoon; a leaked token that can mint more tokens, read another household
member's data, or repoint where your listens are forwarded is a different
category. Requests to them with a token get 401.
Two ways, and neither asks anyone to type a token by hand:
POST /1/pair/start, poll /1/pair/poll while
the user approves the short code in Settings. For clients with no browser.POST /api/v1/auth/login,
then mint a named token. This is the phone's flow. A named token appears
in Settings and can be revoked when the device is lost; a session cookie
expires hard at 7 days with no sliding renewal and cannot be told apart
from any other browser.{ "code": <int>, "error": "<message>" } with a matching HTTP
status.user_id. Scopes
widen who may ask, never whose data is returned.skipped = FALSE).GET /api/openapi.yaml serves this document from the running binary, so a
client can ask its own server what that version supports rather than
guessing from a hosted copy.
The document is generated from the handlers themselves: every schema in it is the type the server actually reads or writes, so a field documented here is a field that exists.
Three mechanisms, and they are not interchangeable. Each endpoint below says which it takes.
header · Authorization Authorization: Token <token>. What the token reaches depends on its
scopes — see the description at the top of this document. Lowercase
token is also accepted.
http · bearer An MCP connection credential, from Settings → AI Connections or from the
OAuth flow. Checked against mcp_connections — not against
tokens, which is a separate credential store for scrobble clients.
Used by POST /mcp and nothing else. A 401 from that endpoint carries
WWW-Authenticate with a resource_metadata pointer, which is how a
client discovers the authorization server.
http · bearer Authorization: Bearer $TAPEDECK_METRICS_TOKEN, for GET /metrics
only. An admin session also works. Never open.
cookie · td_session Set by POST /api/v1/auth/login. HttpOnly, SameSite=Lax, and
Secure when the instance can tell the browser used HTTPS (derived
from X-Forwarded-Proto, and only trusted when trust_proxy is set —
behind a tunnel the origin sees plain HTTP while the client is on TLS).
Expires hard at 7 days with no sliding renewal.
Login, first-run setup, session state.
ListenBrainz-compatible surface under /1/. Shapes follow the LB Core
API because clients branch on those exact fields. Two deliberate
divergences: these require auth and are scoped to the caller (LB serves
them publicly; a self-hosted instance must not), and an invalid token on
validate-token is 200 {valid:false}, never 401, because clients read
the field.
Device-code pairing for clients with no browser.
Patch — following people, on this instance and others. The ActivityPub
surface (/users/…, /.well-known/webfinger) is unauthenticated and lets
another server find out who somebody here is and say something to them; the
rest is the session-only local half: the Reel, decks, Dubs and Shared Spools.
Every federated endpoint is off until a user switches federation on, and an account that has not is indistinguishable from one that does not exist — the same rule the public now-playing widget and device pairing follow.
Your own account — password, display name, bio, profile picture. Scoped to
the caller and never takes an id, unlike the admin routes under
/admin/users. Session-only: a scrobble client has no business changing
the credential it authenticates with.
The scrobble history — read, edit, delete, search.
Aggregations, charts, personality.
Album, artist and entity pages.
Loved recordings, releases and artists.
Liner notes — your own writing about records.
Contiguous listening, grouped.
Signal chains, gear, devices and bindings.
Records, tapes and discs — a shelf you catalogue and sides you play.
Every Noise at Once — coverage, colour, trajectory, neighbours.
Five lists, all built from your own history.
Where listens are polled from. Session-only — holds credentials.
Where listens are forwarded. Session-only — holds credentials.
Bulk in and out. Session-only.
Enrichment backfills and the metadata sanitiser. Session-only.
Preferences and artwork.
Users, tokens, genre-map scrape. Session-only, admin role.
The Model Context Protocol surface — one endpoint that lets an AI assistant read this user's listening and hand playlists back. Runs for every user, reaches nobody's data until they authorise a connection.
Dual-era. Revision 2026-07-28 is stateless with per-request _meta
and mirrored headers; 2025-11-25 and earlier open with an initialize
handshake. Both are served on the same endpoint.
Credentials live in mcp_connections, not in tokens — an MCP grant
is a standing permission given to somebody else's service, tokens.scopes
treats all as a wildcard, and an OAuth credential expires and refreshes.
An OAuth 2.1 authorization server, scoped to one job: letting a hosted
assistant obtain an MCP credential. PKCE S256 only, public clients only,
exact redirect_uri matching, single-use codes.
Playlists that live in Tapedeck, reviewed before they reach a media server, plus the direct push that has always existed.
Records and artists somebody suggested that you do not own — the one place in Tapedeck that names music outside the collection.
Tapedeck does not recommend; it records recommendations made to you. The vision rule governs what Tapedeck generates (Rediscovery is built entirely from your own history and stays that way); this stores what an assistant said, the way notes store what you wrote. It is its own table, its own endpoints and its own page, and never appears in a history or statistics readout.
Health, metrics, log level.