qobuz

πŸ“– Reference

Deep links, the low-level transport, domain types, and error handling

Build URLs that open an entity in the Qobuz app or web player β€” pure string builders, no network.

Every client carries a deep-link helper:

client.deepLink.album("0634904032432") // https://open.qobuz.com/album/0634904032432
client.deepLink.track(33933680)
client.deepLink.playlist(65996412)
client.deepLink.artist(43840)

createDeepLink(base?)

Use it standalone (without a client). base is "open" (default, shareable) or "play" (web player).

import { createDeepLink } from "@kud/qobuz"

const open = createDeepLink() // https://open.qobuz.com/…
const play = createDeepLink("play") // https://play.qobuz.com/…

Returns a DeepLink β€” { album(id), track(id), playlist(id), artist(id) }.

▢️ Now playing

readNowPlayingTrackId(options?)

The id of the currently-playing track, read directly from the Qobuz desktop app's local queue state β€” no API call, no auth. The lower-level primitive behind client.nowPlaying().

import { readNowPlayingTrackId } from "@kud/qobuz"

const trackId = await readNowPlayingTrackId() // number | undefined

Returns undefined if nothing is playing or the state file is absent. options is { path?: string } β€” overrides the default macOS location (defaultPlayerStatePath()). macOS only.

πŸ“Š Collection stats

readLibraryStats(options?)

Collection analytics computed from the Qobuz desktop app's local library database (qobuz.db) β€” no API call, no auth. Reads the SQLite file read-only/immutable (via the system sqlite3), so the running app is undisturbed.

import { readLibraryStats } from "@kud/qobuz"

const stats = await readLibraryStats({ limit: 10 })
// stats.quality      β†’ [{ bitDepth: 24, count: 192 }, …]   (hi-res ratio)
// stats.topGenres    β†’ [{ name: "Metal", count: 484 }, …]
// stats.topLabels    β†’ [{ name: "Nuclear Blast", count: 75 }, …]
// stats.topArtists   β†’ [{ name: "In Flames", count: 10 }, …]  (by offline albums)
// stats.recentlyAddedβ†’ [{ month: "2026-06", count: 51 }, …]
// stats.totals       β†’ { offlineAlbums, offlineArtists, offlineTracks, savedTracks }

Returns a LibraryStats, or undefined if the database (or sqlite3) is absent. options is { path?: string; limit?: number }. macOS only.

Offline vs saved. Quality (quality, topArtists, recentlyAdded) comes from your offline/downloaded tracks; genres and labels come from your broader saved library β€” the two live in different tables, so the type names them explicitly.

πŸ› οΈ Low-level transport

For endpoints not yet wrapped by a resource, drop down to the raw signed-request transport.

createTransport(config)

import { createTransport, QOBUZ_BASE_URL } from "@kud/qobuz"

const transport = createTransport({ appId, token })
const raw = await transport.get("label/get", { label_id: 123 })

config is { appId, token?, baseUrl?, fetchImpl? }. The returned Transport exposes get<T>(path, query?), which injects the X-App-Id / X-User-Auth-Token / User-Agent headers and throws a QobuzError (kind: "http") on non-2xx. QOBUZ_BASE_URL is the default API base.

πŸ“ Domain types

All ids and fields use camelCase. Optional fields are absent when Qobuz doesn't return them.

type Album = {
  id: string // note: album ids are strings
  title: string
  artist?: Artist
  tracksCount?: number
  releaseDate?: string
  duration?: number
  image?: QobuzImage
  genre?: string
  hires?: boolean
}

type Artist = {
  id: number
  name: string
  picture?: string
  albumsCount?: number
}

type Track = {
  id: number
  title: string
  album?: Album
  artist?: Artist
  trackNumber?: number
  duration?: number
  hires?: boolean
}

type Playlist = {
  id: number
  name: string
  description?: string
  tracksCount?: number
  isPublic?: boolean
  owner?: string
  duration?: number
}

type QobuzImage = { thumbnail?: string; small?: string; large?: string }

type SearchResults = {
  query: string
  albums: Album[]
  tracks: Track[]
  artists: Artist[]
}
type UserFavourites = { albums: Album[]; artists: Artist[]; tracks: Track[] }

type FavouriteType = "albums" | "artists" | "tracks"
type PageOptions = { limit?: number; offset?: number }
type CreatePlaylistParams = {
  name: string
  description?: string
  isPublic?: boolean
}

⚠️ Error handling

Every failure throws a QobuzError β€” a standard Error augmented with a kind and an optional HTTP status:

type QobuzError = Error & {
  kind: "http" | "auth" | "bootstrap"
  status?: number
}
kindWhen
authThe token/app_id was rejected (e.g. expired session).
httpA non-2xx response from the API; status carries the code.
bootstrapfetchAppId couldn't scrape the app_id (Qobuz changed its bundle).
import { connect, createMemoryStore, type QobuzError } from "@kud/qobuz"

try {
  await connect({ token, store: createMemoryStore() })
} catch (error) {
  const qobuzError = error as QobuzError
  if (qobuzError.kind === "auth") {
    // prompt the user to reconnect with a fresh token
  }
}

On this page