Skip to content

Getting Tidal Credentials (OAuth Tokens)

V1ck3s edited this page Aug 30, 2026 · 1 revision

This guide explains how to obtain the Tidal OAuth tokens required to configure octo-fiesta with the Tidal provider.

Status. This page documents the native Tidal provider, which talks to Tidal's own API with your account. It is unrelated to the deprecated SquidWTF Tidal backend, which relied on public third-party instances holding somebody else's credentials.

Prerequisites

  • An active Tidal subscription. A free or expired account can browse and search but cannot stream full tracks.
  • A Tidal HiFi Plus plan if you want HI_RES_LOSSLESS. Standard HiFi tops out at LOSSLESS.
  • A web browser to approve the device authorization.
  • A writable path for the token store, so the refresh token survives restarts.

How the login works

Tidal does not expose a copy-paste cookie like Deezer or a token in local storage like Qobuz. It uses an OAuth 2.0 device authorization flow, the same one used by TV and console apps.

  1. octo-fiesta asks auth.tidal.com for a device code
  2. You open a short link in your browser and approve the request with your Tidal account
  3. octo-fiesta polls until you approve, then receives an access token and a refresh token
  4. The access token expires after four hours and is renewed automatically with the refresh token

You only do the browser step once. After that the provider keeps itself authenticated.

Step 1 - Run the login helper

Run the interactive login. It prints a link, waits for you to approve it, and writes the resulting tokens to the token store.

Using Docker

docker compose run --rm octo-fiesta --tidal-login

Running from source

dotnet run -- --tidal-login

Step 2 - Approve the device in your browser

The helper prints something like this.

Open https://link.tidal.com/ABC12 and log in with your Tidal account.
Waiting for approval... (expires in 5 minutes)

Open the link, sign in if prompted, and confirm the device. The helper detects the approval within a few seconds and prints a summary.

Tidal login successful.
  User ID: 123456789
  Country: FR
  Subscription: HIFI_PLUS
  Token store: /config/tidal-tokens.json

If the link expires before you approve it, run the helper again to get a fresh one.

Step 3 - Configure octo-fiesta

Select Tidal as the music service. The tokens themselves live in the token store and do not need to be pasted anywhere.

Using Docker (.env file):

MUSIC_SERVICE=Tidal
TIDAL_TOKEN_STORE=/config/tidal-tokens.json
TIDAL_QUALITY=LOSSLESS  # Optional: HI_RES_LOSSLESS, LOSSLESS, HIGH, LOW

Mount a volume so the token store persists across container restarts. Without it you have to log in again on every recreate. The bundled docker-compose.yml already mounts ./config on /config for this.

services:
  octo-fiesta:
    volumes:
      - ./config:/config

Using appsettings.json:

{
  "Subsonic": {
    "MusicService": "Tidal"
  },
  "Tidal": {
    "TokenStore": "/config/tidal-tokens.json",
    "Quality": "LOSSLESS"
  }
}

Changing the device client

octo-fiesta identifies itself to Tidal with a device client id and secret. This is an application credential, not an account one, so it is the same for everyone and the login does not change it. The built-in pair works today.

If Tidal retires it, the symptom is unmistakable. Login and search keep working while every download fails with subStatus 4005 and the token stops renewing with Client id <n> not found. You can then switch to a working client without waiting for a release.

TIDAL_CLIENT_ID=theWorkingClientId
TIDAL_CLIENT_SECRET=theMatchingSecret

Tokens belong to the client that issued them, so after the change the stored tokens are ignored and you have to run the login helper again.

Supplying tokens directly

If you already have Tidal tokens from another tool, or you prefer to inject them as secrets rather than use the token store, set them explicitly. Values given this way take precedence over the token store on startup.

TIDAL_ACCESS_TOKEN=your_access_token_here
TIDAL_REFRESH_TOKEN=your_refresh_token_here
TIDAL_USER_ID=123456789
TIDAL_COUNTRY_CODE=FR

The refresh token is the important one. The access token is short-lived and is replaced automatically. If you provide a refresh token but no access token, octo-fiesta mints a fresh access token on startup. Renewals are still written to the token store, so keep it writable.

Step 4 - Verify configuration

When you start octo-fiesta, the startup validation reports the account the provider will use.

┌─ Tidal ────────────────────────────────────────────────────┐
  Tidal Quality: LOSSLESS
  Tidal Token store: /config/tidal-tokens.json
  Tidal AccessToken: eyJ0********
  Tidal credentials: VALID
    -> User ID: 123456789
    -> Country: FR
    -> Subscription: HIFI_PLUS
    -> Highest available quality: HI_RES_LOSSLESS
└────────────────────────────────────────────────────────────┘

If the refresh token has been revoked, an error is shown and you need to run the login helper again.

An account without an active subscription authenticates fine but cannot stream, which is reported separately.

  Tidal credentials: VALID
    -> Subscription: FREE
  Tidal playback: NOT ENTITLED
    -> This account has no active subscription, so no track can be downloaded

Quality Options

Value Format Bit Depth Sample Rate Requirements
HI_RES_LOSSLESS FLAC 24-bit up to 192kHz HiFi Plus
LOSSLESS FLAC 16-bit 44.1kHz HiFi
HIGH AAC - 320 kbps Any paid plan
LOW AAC - 96 kbps Any paid plan

If not specified, octo-fiesta asks for HI_RES_LOSSLESS. Tidal usually answers with the best tier your subscription is entitled to rather than refusing, so asking high costs nothing. A tier that is refused outright makes octo-fiesta step down one level and try again.

The setting is a ceiling, not a guarantee. A PREMIUM plan asked for HI_RES_LOSSLESS is served HIGH, and the downloaded file is labelled from what Tidal actually delivered, not from what was requested.

HI_RES is also accepted for compatibility with older configurations. It maps to the legacy MQA-encoded tier, which Tidal has been retiring in favour of HI_RES_LOSSLESS. Prefer HI_RES_LOSSLESS for true 24-bit FLAC.

Hi-Res tracks arrive as FLAC inside a fragmented MP4 container and are saved as .m4a. The audio is lossless, only the container differs.

Token Lifetime

  • The access token is valid for four hours. octo-fiesta renews it in the background before it expires, so you never notice.
  • The refresh token has no expiry of its own, but changing your Tidal password or signing the device out from the Tidal app revokes it.
  • Every renewal is written back to the token store, so the store must stay writable for the lifetime of the install.

Troubleshooting

"Invalid credentials" error

  • The refresh token may have been revoked. Run the login helper again to get a fresh pair.
  • Check that the token store path is writable. A read-only mount silently breaks renewal, and the provider stops working once the stored access token expires.

"Asset is not ready for playback" errors

  • octo-fiesta retries one tier lower on its own. If a single track fails while others work, that track is not streamable for your account.
  • If every track fails on every tier, the account has no playback entitlement. Startup validation reports this as Tidal playback: NOT ENTITLED.

"Track unavailable" errors

  • Tidal catalogues differ by country. The country code comes from your account and cannot be overridden to bypass a regional restriction.

Quality lower than expected

  • Verify your plan actually includes HiFi Plus. Many tracks are also simply not published in Hi-Res, in which case the provider falls back to the best tier available.

Downloads stop working after a restart

  • This is the signature of a token store that is not persisted. Confirm the volume mount and that the file survives a container restart.

Search works but every download fails

  • Startup validation says Tidal playback: NOT ENTITLED when the account has no active subscription. Tidal then answers 401 with subStatus 4005 on every quality tier, and even preview clips are refused. Subscribing is the fix.

Technical Reference

For anyone working on the provider itself, these are the endpoints involved.

Purpose Endpoint
Device authorization POST https://auth.tidal.com/v1/oauth2/device_authorization
Token exchange and refresh POST https://auth.tidal.com/v1/oauth2/token
Session check GET https://api.tidal.com/v1/sessions
Subscription GET https://api.tidal.com/v1/users/{userId}/subscription
Catalogue and search https://api.tidal.com/v1
Stream manifest GET https://api.tidal.com/v1/tracks/{id}/playbackinfopostpaywall

Notes on the flow.

  • The device authorization call takes a client_id and the scope r_usr+w_usr+w_sub. The token calls additionally need the client secret of a Tidal device client, which is embedded in octo-fiesta rather than configured by the user. The secret goes in the form body, not in an HTTP Basic header, because Tidal's CDN rejects Basic-authenticated token calls with a 403 before they reach the API. Sending the credentials in the body reaches the API and is accepted for the device code grant.
  • Requests need a device-client user agent for the same reason.
  • While polling, Tidal answers HTTP 400 with sub_status 1002 for as long as the user has not approved yet. Any other error status means the flow failed and should not be retried.
  • A refresh grant returns a new access token but no new refresh token, so the existing one is kept.
  • Every catalogue endpoint requires a countryCode query parameter. It comes from the account and is cached in the token store.
  • playbackinfopostpaywall takes audioquality, playbackmode=STREAM and assetpresentation=FULL. It returns a base64-encoded manifest containing the stream URLs, the codec, and a keyId when the stream is encrypted. The response repeats the audioQuality actually granted, which is often lower than the one requested, so that is the value the file is labelled with. A tier refused outright answers 401 with subStatus 4005, as does a manifest that fails to decode or comes back as a preview clip, and the request is then retried one tier lower.
  • Tidal retires device clients over time. A retired client still completes the device flow and reads the catalogue, but its stream requests answer subStatus 4005 and its refresh grant answers Client id <n> not found after parsing a token it has just issued. When changing the embedded client, test playback and renewal, not just login.
  • Playlist payloads carry created with the offset written as +0000 rather than +00:00, which System.Text.Json refuses to bind to a date. The field is read as text and parsed separately.
  • Hi-Res streams arrive as a DASH manifest rather than a single file, which TidalDashManifestParser flattens into an ordered segment list.

Note: Unlike the deprecated SquidWTF provider, this one streams from your own Tidal account. Your subscription tier, and not a shared public instance, determines what you can download.

Clone this wiki locally