← API reference

Connections

Where listens are forwarded. Session-only — holds credentials.

15 of 15 · v0.120.0
GET /api/v1/tidal/status #
Session

Whether TIDAL is configured and connected.

Session-only. configured is about the instance — whether the operator registered an app at developer.tidal.com and set TAPEDECK_TIDAL_CLIENT_ID — and connected is about this user. They are separate because an instance that cannot offer the button at all is a different thing from an account nobody has linked yet.

Responses

200

OK.

application/json

object

Whether TIDAL can be used here, and whether you have.

configured boolean required

The operator has registered an app. Without it there is no button to offer — a different thing from an account nobody has linked.

connected boolean required
401

No valid session cookie or token. Also returned when a token is presented to a session-only endpoint — the endpoint does not accept tokens at all, so the scope is irrelevant.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

{
  "code": 401,
  "error": "Authentication required. Log in to access this endpoint."
}
POST /api/v1/tidal/connect #
Session

Begin connecting a TIDAL account.

Session-only. Returns the URL to open. Authorization Code + PKCE with S256; the verifier is held in memory for ten minutes and the state token is single-use.

Scopes asked for are the minimum a push needs — playlists.write, playlists.read, search.read, user.read — and no collection.* or playback.

Responses

200

OK.

application/json

object

Where to send the browser.

authorize_url string required

TIDAL's authorize URL, carrying a PKCE S256 challenge and a single-use state that expires in ten minutes. Open it in a new tab.

401

No valid session cookie or token. Also returned when a token is presented to a session-only endpoint — the endpoint does not accept tokens at all, so the scope is irrelevant.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

{
  "code": 401,
  "error": "Authentication required. Log in to access this endpoint."
}
501

TIDAL is not configured on this instance.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

GET /api/v1/tidal/callback #
Public

Where TIDAL sends the browser back.

Takes no session. The owner comes from the single-use state token minted at connect time, which is also the CSRF protection; requiring a cookie here would additionally depend on it surviving a cross-site navigation from login.tidal.com. Redirects to Settings either way — the user declining is a normal outcome, not an error.

Parameters

code query
string
state query

The single-use token minted by connect. It identifies the account.

string
error query

Set when the user declined.

string
error_description query
string

Responses

303

Back to Settings, with tidal=connected or tidal=denied.

400

The sign-in expired, was already used, or carried no code.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

502

TIDAL refused the token exchange.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

DELETE /api/v1/tidal/disconnect #
Session

Forget the connected TIDAL account.

Session-only.

Responses

200

OK.

application/json

object
disconnected boolean required

Whether there was a connection to forget.

401

No valid session cookie or token. Also returned when a token is presented to a session-only endpoint — the endpoint does not accept tokens at all, so the scope is irrelevant.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

{
  "code": 401,
  "error": "Authentication required. Log in to access this endpoint."
}
GET /api/v1/tidal/settings #
Session

The TIDAL app registration for this server.

Admin only. Never returns the credential itself, the same rule the Discogs pair and every source credential follow. stored and from_env are reported separately so the screen can say which is in effect rather than leaving an operator wondering why an exported variable is being ignored — a stored value wins.

redirect_uri is the exact string TIDAL must have registered, derived from PUBLIC_URL so it is the same one the flow will actually send.

Responses

200

OK.

application/json

object

The app registration, without its credentials.

configured boolean required

Stored or from the environment.

stored boolean required

A client id is stored in the database — which wins over the environment.

has_secret boolean required

A stored client id has a stored secret beside it.

from_env boolean required

TAPEDECK_TIDAL_CLIENT_ID and its secret are set.

redirect_uri string required

The exact redirect URI TIDAL must have registered — the one the flow will send.

401

No valid session cookie or token. Also returned when a token is presented to a session-only endpoint — the endpoint does not accept tokens at all, so the scope is irrelevant.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

{
  "code": 401,
  "error": "Authentication required. Log in to access this endpoint."
}
403

Authenticated, but not permitted. Either the token lacks the required scope, or the endpoint needs the admin role. Deliberately not a 401 — re-authenticating will not help.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

{
  "code": 403,
  "error": "This token does not have the 'write' scope"
}
PUT /api/v1/tidal/settings #
Session

Store the TIDAL app registration.

Admin only. Each field is written only when present, so saving one does not wipe the other; clearing is an explicit empty string. Stored encrypted at rest.

Changing the client id disconnects every account on the instance — the stored tokens were issued by the old app and are meaningless to a new one, so keeping them would offer a connection that fails on the next push.

Request body required

application/json

object

Each field is written only when present, so saving one does not wipe the other. An explicit empty string clears.

client_id string | null
client_secret string | null

Responses

200

OK.

application/json

object

Whether TIDAL is configured now.

configured boolean required
401

No valid session cookie or token. Also returned when a token is presented to a session-only endpoint — the endpoint does not accept tokens at all, so the scope is irrelevant.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

{
  "code": 401,
  "error": "Authentication required. Log in to access this endpoint."
}
403

Authenticated, but not permitted. Either the token lacks the required scope, or the endpoint needs the admin role. Deliberately not a 401 — re-authenticating will not help.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

{
  "code": 403,
  "error": "This token does not have the 'write' scope"
}
GET /api/v1/connections #
Session

Where your listens are forwarded.

Session-only, permanently — holds credentials.

Forwarding is strictly per user. A user with no connections keeps their listens local; there is deliberately no server-wide fallback, which previously sent every unconnected user's listens to the operator's own account.

Responses

200

OK.

application/json

object

Your forwarding connections. Never carries a token or session key.

connections array required
each item
object
service string required

listenbrainz, lastfm or librefm.

username string | null

The account on that service, where it said.

connected_at integer · int64 required
lastfm_available boolean required
401

No valid session cookie or token. Also returned when a token is presented to a session-only endpoint — the endpoint does not accept tokens at all, so the scope is irrelevant.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

{
  "code": 401,
  "error": "Authentication required. Log in to access this endpoint."
}
GET /api/v1/connections/spool-forwarding #
Session

Whether accepted Shared Spool plays are forwarded onward.

enabled is the switch; window_days is the freshness bound that applies even when it is on, reported rather than hardcoded in a client so the two cannot disagree about what will actually be relayed.

Responses

200

The switch and its window.

application/json

object

Whether plays accepted out of a Shared Spool are forwarded.

enabled boolean required
window_days integer | null · int64

Only plays this recent are relayed, whatever the switch says. Absent from the answer to a change.

401

No valid session cookie or token. Also returned when a token is presented to a session-only endpoint — the endpoint does not accept tokens at all, so the scope is irrelevant.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

{
  "code": 401,
  "error": "Authentication required. Log in to access this endpoint."
}
PUT /api/v1/connections/spool-forwarding #
Session

Set it.

Off by default, and off for every account that predates it. Turning it on means a play somebody else's deck made — which you were in the room for, and accepted one at a time — is relayed to your Last.fm and ListenBrainz like any listen of your own.

A play older than window_days is stored and never relayed even with the switch on. retro_spool_from_session can offer an evening of any age and an unanswered invitation can be joined a month later, so without that bound a single Accept could rewrite a year of a public history.

Session-only; a token gets 401 and scopes never enter into it. A scrobble client has no business deciding that another deck may write to its owner's public record.

Request body required

application/json

object

The switch deciding whether plays accepted out of a Shared Spool are relayed on to this user's connections, or stored here only.

It lives beside the connections rather than with the rest of Patch because this is the question "what do my connected services receive", and the answer is only meaningful next to the list of them.

enabled boolean required

Required, and said explicitly — an absent key is a 400, not a default.

Responses

200

The switch as it now stands.

application/json

object

Whether plays accepted out of a Shared Spool are forwarded.

enabled boolean required
window_days integer | null · int64

Only plays this recent are relayed, whatever the switch says. Absent from the answer to a change.

400

enabled was absent.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

401

No valid session cookie or token. Also returned when a token is presented to a session-only endpoint — the endpoint does not accept tokens at all, so the scope is irrelevant.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

{
  "code": 401,
  "error": "Authentication required. Log in to access this endpoint."
}
DELETE /api/v1/connections/{service} #
Session

Disconnect a service.

Session-only.

Parameters

service path required

lastfm, librefm or listenbrainz.

string

Responses

204

Disconnected, or was never connected.

400

Malformed or rejected input.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

401

No valid session cookie or token. Also returned when a token is presented to a session-only endpoint — the endpoint does not accept tokens at all, so the scope is irrelevant.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

{
  "code": 401,
  "error": "Authentication required. Log in to access this endpoint."
}
PUT /api/v1/connections/listenbrainz #
Session

Store a ListenBrainz token.

Session-only. Stored encrypted. The endpoint is configurable for self-hosted ListenBrainz instances — that is the one scrobble setting still in the config file rather than the database.

Request body required

application/json

object
token string required

Your ListenBrainz user token. Checked against ListenBrainz before it is stored.

Responses

200

Connected.

application/json

object
connected boolean required
username string | null
400

Malformed or rejected input.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

401

No valid session cookie or token. Also returned when a token is presented to a session-only endpoint — the endpoint does not accept tokens at all, so the scope is irrelevant.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

{
  "code": 401,
  "error": "Authentication required. Log in to access this endpoint."
}
502

ListenBrainz could not be reached.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

POST /api/v1/connections/lastfm/start #
Session

Begin the Last.fm connect flow.

Session-only. Returns a URL to send the browser to.

Responses

200

OK.

application/json

object

Where to send the browser to approve Tapedeck.

token string required

The request token — send it back to …/complete once approved.

auth_url string required
401

No valid session cookie or token. Also returned when a token is presented to a session-only endpoint — the endpoint does not accept tokens at all, so the scope is irrelevant.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

{
  "code": 401,
  "error": "Authentication required. Log in to access this endpoint."
}
502

The service could not be reached, or refused a token.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

POST /api/v1/connections/lastfm/complete #
Session

Finish the Last.fm connect flow.

Session-only. Exchanges the approved request token for a session key, stored encrypted.

Request body required

application/json

object
token string required

The token …/start returned, after you approved it.

Responses

200

Connected.

application/json

object
connected boolean required
username string | null
400

Not approved yet, or no token.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

401

No valid session cookie or token. Also returned when a token is presented to a session-only endpoint — the endpoint does not accept tokens at all, so the scope is irrelevant.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

{
  "code": 401,
  "error": "Authentication required. Log in to access this endpoint."
}
502

The service could not be reached.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

POST /api/v1/connections/librefm/start #
Session

Begin the Libre.fm connect flow.

Session-only. GNU FM implements the same AudioScrobbler auth, so this reuses the Last.fm app credentials.

Responses

200

OK.

application/json

object

Where to send the browser to approve Tapedeck.

token string required

The request token — send it back to …/complete once approved.

auth_url string required
401

No valid session cookie or token. Also returned when a token is presented to a session-only endpoint — the endpoint does not accept tokens at all, so the scope is irrelevant.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

{
  "code": 401,
  "error": "Authentication required. Log in to access this endpoint."
}
502

The service could not be reached, or refused a token.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

POST /api/v1/connections/librefm/complete #
Session

Finish the Libre.fm connect flow.

Session-only.

Request body required

application/json

object
token string required

The token …/start returned, after you approved it.

Responses

200

Connected.

application/json

object
connected boolean required
username string | null
400

Not approved yet, or no token.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

401

No valid session cookie or token. Also returned when a token is presented to a session-only endpoint — the endpoint does not accept tokens at all, so the scope is irrelevant.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

{
  "code": 401,
  "error": "Authentication required. Log in to access this endpoint."
}
502

The service could not be reached.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.