An unofficial Rust library for interacting with the TIDAL music streaming API.
This library has API support for tracks, albums, artists, videos, playlists, collection, mixes, search, user, subscription information and much more...
- Audio quality support: Low, High, Lossless, HiRes (only with PKCE auth)
- Multiple auth flows:
- OAuth2 device-code flow (
TidalAuth::with_oauth()) - OAuth2 PKCE flow for HiRes streaming (
TidalAuth::with_pkce()) - Client-credentials flow (
TidalAuth::with_api_token(...)) - Direct access token (
TidalAuth::with_access_token(...))
- OAuth2 device-code flow (
- DASH manifest parsing for HiRes playback
- Session persistence (
get_json()/from_json()) tracingfor auth/session/request flows
Tidlers is in active development, here are some projects using it:
- Maré Player - COSMIC TIDAL applet/standalone app
- yadal - Command-line downloader with parallel downloads and all quality support
cargo add tidlersOr use the latest git version:
[dependencies]
tidlers = { git = "https://codeberg.org/tomkoid/tidlers.git" }Minimal PKCE login example.
cargo run -p pkce-loginLogin + session persistence usage.
cargo run -p login-saveHiRes streaming and DASH manifest usage.
cargo run -p hires-streameruse tidlers::{auth::TidalAuth, TidalClient};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let auth = TidalAuth::with_oauth();
let mut client = TidalClient::new(&auth);
let oauth = client.get_oauth_link().await?;
println!("Visit: {}", oauth.verification_uri_complete);
client
.wait_for_oauth(
&oauth.device_code,
oauth.expires_in,
oauth.interval,
None,
)
.await?;
let me = client.refresh_user_info().await?;
println!("Logged in as: {}", me.username);
Ok(())
}use tidlers::{auth::TidalAuth, TidalClient};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let auth = TidalAuth::with_pkce();
let mut client = TidalClient::new(&auth);
let login_url = client.initiate_pkce_login()?;
println!("Visit: {}", login_url);
// After browser redirect, paste the full redirect URL:
let mut redirect_url = String::new();
std::io::stdin().read_line(&mut redirect_url)?;
client.finish_pkce_login(redirect_url.trim()).await?;
client.refresh_user_info().await?;
Ok(())
}let session_json = client.get_json();
std::fs::write("session.json", session_json)?;
let session_data = std::fs::read_to_string("session.json")?;
let mut client = TidalClient::from_json(&session_data)?;
client.refresh_access_token(false).await?;
client.refresh_user_info().await?;use tidlers::client::models::playback::AudioQuality;
let track = client.get_track("66035607").await?;
println!("{} - {}", track.artist.name, track.title);
client.set_audio_quality(AudioQuality::HiRes);
let playback = client.get_track_postpaywall_playback_info("66035607", None).await?;
if let Some(urls) = playback.get_stream_urls() {
println!("Stream URLs: {urls:?}");
}let album = client.get_album("251380836").await?;
println!("Album: {}", album.title);
let items = client
.get_album_items("251380836", Some(50), Some(0))
.await?;
println!("Album items: {}", items.items.len());use tidlers::client::models::playlist::{OrderDirection, PlaylistItemsOrder};
let playlist = client.get_playlist("YOUR_PLAYLIST_UUID").await?;
println!("Playlist: {}", playlist.title);
let playlist_items = client
.get_playlist_items(
"YOUR_PLAYLIST_UUID",
Some(100),
Some(0),
PlaylistItemsOrder::Index,
OrderDirection::Ascending,
)
.await?;
println!("Playlist items: {}", playlist_items.items.len());use tidlers::client::models::search::config::SearchType;
let results = client
.search_direct("daft punk", Some(vec![SearchType::Tracks]), Some(10), Some(0))
.await?;
println!("Found {} top hits", results.top_hits.items.len());let subscription = client.subscription().await?;
println!(
"Subscription type: {:?}",
subscription.subscription.subscription_type
);
let mix = client.get_track_mix("66035607", Some(20), Some(0)).await?;
println!("Mix items: {}", mix.items.len());Tidlers emits logs via tracing. Example subscriber:
use tracing_subscriber::{fmt, EnvFilter};
fn init_tracing() {
let _ = fmt()
.with_env_filter(
EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("tidlers=debug")),
)
.try_init();
}Then run with:
RUST_LOG=tidlers=debug cargo run -p testing-client -- user-infoIf you have a feature/suggestion/fix that would make this project better, please fork the repo and create a pull request or simply open an issue on Codeberg. I'm very open to new features.
cargo build
cargo check --workspace --all-targets
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace --all-targets- OAuth device-code and PKCE flows require browser/user interaction
- Many endpoints are country-scoped; after restoring a session, call
refresh_user_info()before country-scoped requests - Rate limiting is not implemented
- Some parts of code and documentation are written using AI
This project is for educational and personal use. Ensure compliance with TIDAL's Terms of Service.
This is an unofficial library and is not affiliated with or endorsed by TIDAL. Use at your own risk.