# Privacy, and what Stash talks to The long version, for anyone who wants every detail. The short version is in the [README](README.md#faq): there's no Stash account, no analytics, and your logins stay on your phone. ## The basics - No Stash account server, no login, no analytics, no third-party crash reporters. A handful of hosts the project runs are fetched from; none of them ever sees a credential. - Cookies stored on-device, encrypted with AES-256-GCM via Google's [Tink](https://developers.google.com/tink). When yt-dlp runs, it gets a temporary copy of your YouTube cookie in a private file only Stash can read, deleted as soon as it's done - Your Spotify, YouTube, and Discord credentials are sent to Spotify, YouTube, and Discord respectively and nowhere else — no host in the list below ever sees them - GPL-3.0-or-later, every line of code is open source ## Where lossless comes from There's no Qobuz or Tidal account or token inside the app. FLAC comes from Qobuz or Tidal, through the first of these that can serve the song: - **Your own Qobuz account** — connect it in Settings › Audio & Quality. Connecting asks for your Qobuz email and password, which are sent once to Qobuz to mint a token; Stash stores the token, not the password. It then streams and downloads from your own subscription, the same catalog your Qobuz app sees. - **Your own relay endpoint** — if you run a Qobuz relay, paste its URL into the custom-endpoint field (under Advanced on the same screen) and Stash routes through it. - **Stash's relay** — the official release build fetches a signed config at each cold start that lists `stash-relay.rawnaldclark.workers.dev`, a relay the project runs. With nothing of your own connected, Stash uses it for FLAC. Connect your own account and your phone uses your account first, falling back to the relay only for a song whose request on your account fails, or while Qobuz is rejecting your login. It hands your phone a short-lived Qobuz CDN link for the track; the audio comes from Qobuz's CDN, never through the relay. [What Stash talks to](#what-stash-talks-to) below says what it's sent. The relay hostname is not in the APK — a plain source checkout has no config URL, so a Stash you build yourself has this path switched off entirely. - **Stash's Tidal relay** — the official release build can also ask a Tidal relay the project runs. Like the Qobuz relay, it hands your phone a short-lived link and the audio comes from Tidal's CDN; only the first small setup piece of some streams (the part that describes the file) passes through the relay so your phone can play it. It is sent the Tidal track id, whether you want AAC, and the same random install id, never a credential. Tidal is also where the **AAC 320** download quality comes from. Every install gets 500 lossless songs a day, Qobuz and Tidal together, counted by your random install id. Past that, Stash waits until the next day or, with the lossy fallback on, plays from YouTube. When no lossless source can serve a song, it streams in AAC or Opus instead, and Now Playing shows where it came from. Downloads wait for FLAC by default and show as "Waiting for lossless"; to take an AAC or Opus copy instead, turn on **Lossy fallback** in Settings › Audio & Quality. ## What Stash talks to Stash has no account server, but it isn't a two-service app either. Everything the official release APK can reach, and what for. A build you make yourself from a plain checkout reaches strictly less: it has no Last.fm keys or proxy and no relay config. - **Spotify** (`accounts.`, `open.`, `api-partner.`, `api.spotify.com`, `www.spotify.com`, `clienttoken.spotify.com`) — sign-in and library sync. Two optional features, both off by default, also write to your account: mirroring your likes, and auto-saving songs you play often to your Liked Songs. - **YouTube + YouTube Music** (`music.youtube.com`, `www.youtube.com`, and Google's OAuth endpoints if you use the Google sign-in) — library sync, search, artist and album pages, artist photos, and the audio itself via yt-dlp. Two optional features, both off by default, also write to your account: mirroring your likes, and sending your plays to your YouTube Music history. Each play is reported to the tracking address that comes back in YouTube's player response. - **`m.youtube.com`** — the YouTube sign-in window - **`*.googlevideo.com`** — YouTube's audio CDN; the URL comes back in the player response, so no hostname for it ships in the app - **Google OAuth** (`oauth2.googleapis.com`) — only if you use the YouTube device-code sign-in - **What the sign-in windows load** — the Spotify, YouTube and Discord sign-in windows show those services' own web pages, and the pages load what they need from other hosts, such as Spotify's `accounts.scdn.co`, Google reCAPTCHA (`www.google.com`, `www.gstatic.com`), Google's sign-in pages, and Discord's image CDN and CAPTCHA. Stash itself never calls those hosts; the pages do. - **GitHub** (`api.github.com`) — the update checks, for Stash itself and for yt-dlp's nightly builds, at every cold start and daily - **GitHub release downloads** (`github.com`, `release-assets.githubusercontent.com`) — a newer yt-dlp nightly build when there is one, and a helper script yt-dlp needs for YouTube playback, whether or not you use streaming - **Qobuz** — catalog requests go straight from your phone to `www.qobuz.com`, with no account, under Qobuz's public web-player app id. Stash also contacts Qobuz's web player, `open.qobuz.com`, when that id needs refreshing and when you connect an account; that host is sent nothing about you. - **On Home** — the New Releases, Top Albums and Qobuz Playlists rows. No account needed and **on by default**; turning off "Qobuz discovery on Home" in Settings › Library & Storage, or hiding those rows in Home layout, stops these requests. - **On artist pages** — Qobuz is sent the artist's name, to fill in albums YouTube is missing, and an album's id when you open one of those albums. This runs whatever the Home setting is, and no setting turns it off. - **Lossless lookups** — while Lossless is on, which is the default, every song about to stream (including the next one up) or about to download is looked up here first, by its ISRC or by artist and title. This happens with or without an account, and even when the song then plays from somewhere else, so Qobuz learns what you play. Turning Lossless off in Settings › Audio & Quality stops these lookups, except when you ask for FLAC yourself with "Find in FLAC" in Now Playing or "Upgrade to FLAC" in Library. - **Your own account** — only if you connect one in Settings › Audio & Quality. Connecting sends your Qobuz email and password to Qobuz once, to get a token; Stash keeps the token and your email, not the password. Each FLAC link is then requested with that token. While Lossless is on and your account is working, songs you scroll past in search results and on artist and album pages are also looked up ahead of time, so they start faster. - **`stash-relay.rawnaldclark.workers.dev`** — the project's lossless relay, used only by the official release build. With no Qobuz account or relay endpoint of your own, Stash asks it for every FLAC link. With your own account connected, Stash asks your account first. It uses the relay for a song when the request on your account fails, and for every song while Qobuz is rejecting your login. Your login is retried once a minute, so a login Qobuz no longer accepts keeps using the relay until you sign in again or disconnect it. With your own relay endpoint set, the project's relay is used when yours fails. - **What it is sent and keeps** — the Qobuz track id and format of each song, whether it's a download (Stash marks sync, playlist and mix downloads, their retries and its FLAC upgrade passes; anything else counts as playing, including songs you download from Search or an artist or album page), and a random id for your install. Nothing else about you, and never a credential, so this host learns what an anonymous install listens to. Like any server, it sees your IP address, and it uses it for a per-address rate limit. When it has to ask Qobuz for a new link instead of reusing a recent one, it adds one to a daily count for your install id, stored with a scrambled form of your IP address (a salted hash). That enforces the daily limits and shows when one connection poses as many installs. These counts are kept for the current day and the 7 before it, then deleted, and stay restorable by the owner from Cloudflare's database backups for up to 30 days. The relay's log line for each of those requests shows the track, the format, the first 8 characters of your install id, whether it's a download, your country, and which Cloudflare location answered. The relay hands back a short-lived link, never the audio. If you set your own relay endpoint instead, it is sent the same, and what it keeps is up to whoever runs it. - **Qobuz's audio CDN** (today `streaming-qobuz-std.akamaized.net`) — where the FLAC itself comes from, whether the link came from your account or the project's relay. The address comes back with each link, so it can change. It sees your IP address and which file you're getting. Share diagnostics, when you use it, also checks that this CDN, the relay and the relay config host answer. - **`stash-tipjar.rawnaldclark.workers.dev/lossless.json`** — the signed relay config and its `.sig`, fetched at every cold start and every 6 hours after by the official release build. This URL *is* in the APK; it is fetched with nothing of yours connected, like the tip jar list on the same host. A plain checkout has no config URL and skips it. - **JioSaavn** (`www.jiosaavn.com`, `aac.saavncdn.com`) — the AAC 320 fallback when nothing lossless matched - **LRCLIB** (`lrclib.net`) — synced lyrics - **iTunes Search** (`itunes.apple.com`) — the first step of word-synced Apple Music lyrics: sent the song's title and artist to find its Apple Music song id - **paxsenix's lyrics API** (`lyrics.paxsenix.org`) — the second step: sent that Apple song id, and nothing else about you, to get the word-synced lyrics (its cache first, then a live lookup) - **KuGou** (`mobileservice.kugou.com`, `lyrics.kugou.com`) — synced lyrics when the sources before it have none: sent the song's title and artist, and for some lookups its length. It sees an ordinary desktop-browser User-Agent, not Stash's. - **YouTube Music lyrics** (`music.youtube.com`) — plain-text lyrics as the last fallback, only for songs that have a YouTube id - **When each lyrics host is asked** — LRCLIB, iTunes Search and paxsenix see a `Stash/` User-Agent. iTunes Search and paxsenix are asked whenever lyrics are looked up or fetched for a download while Apple Music is the preferred lyrics source, which is the default. They're also asked by a background pass that upgrades lyrics you already have (once after each app update, on Wi-Fi) and by "Fetch lyrics" in Library Health. LRCLIB, KuGou and YouTube Music come next, in that order, each only when the ones before it come back empty or fail: from Now Playing, after a download, and by "Fetch lyrics", never by the background pass. Setting the lyrics source to LRC only in Settings › Playback stops iTunes Search and paxsenix. LRCLIB, KuGou and YouTube Music keep working. - **Last.fm** (`ws.audioscrobbler.com`, and `www.last.fm` for its sign-in page) — scrobbling, only if you connect your account, and loved tracks if you also turn on mirroring likes to Last.fm. Official release builds also look things up on Last.fm without needing an account: artist bios on artist pages, missing album art, genre tags for your downloads, and the similar artists, songs and genres behind radios and Stash Mixes (see below). Those lookups go through `stash-lastfm-proxy.rawnaldclark.workers.dev`, a caching Worker the project runs. It sees the artist or song being looked up, never your account, and keeps only Last.fm's answers, for up to 14 days. If the proxy fails, Stash asks Last.fm directly. With your account connected and Stash Mixes on, Stash also reads your top artists and songs, and looks up your downloaded songs under your username, straight from Last.fm. - **ListenBrainz** (`api.listenbrainz.org`) — optional scrobbling, only if you connect it - **Discord** (`discord.com`, `cdn.discordapp.com`) — optional Rich Presence, only if you connect your account. While a song plays, Stash sends Discord its title, artist, album, a link to its cover and how far into the song you are; pausing clears it. Stash sets this status through your account's own session, which it gets from the sign-in window. If Discord needs your permission first, Settings opens Discord's permission page in your browser. Agreeing sends your browser on to a blank page at `paraliyzed.net`, with a one-time code in the address that Stash doesn't use. See the callout in [First-time setup](README.md#first-time-setup) for exactly how it works and what that means. - **MusicBrainz** (`musicbrainz.org`) — artist metadata - **Dropbox** (`www.dropbox.com` for its sign-in page, `api.dropboxapi.com`, and the `*.dropboxusercontent.com` links your files are read from) — only after you connect your Dropbox in Settings › Accounts. Stash asks for read-only access to your files (`files.metadata.read`, `files.content.read`) and never writes to your Dropbox. It lists the folders you pick, reads the start of each new or changed song (a few hundred KB, sometimes its end too) for its tags and cover, and fetches a song's bytes when you play or download it. Dropbox sees which files you open and your IP address. The sign-in tokens are stored encrypted on your phone and are sent only to Dropbox; disconnecting forgets them. Nothing about your Dropbox goes to any Stash server: the audio comes straight from Dropbox to your phone. - **OneDrive** (`login.microsoftonline.com` for the Microsoft sign-in, `graph.microsoft.com`, and the download host each file's link points to, such as `*.sharepoint.com` or `*.files.1drv.com`) — only after you connect your OneDrive in Settings › Accounts, and only in builds that ship a Microsoft app id. Read-only access (`Files.Read`, plus `offline_access` and `User.Read` to stay signed in and show your name); Stash never writes to your OneDrive. It does the same as for Dropbox: lists the folders you pick, reads the start of new or changed songs for their tags and cover, and fetches a song when you play or download it. Microsoft sees which files you open and your IP address. Tokens are stored encrypted on your phone, sent only to Microsoft, and forgotten when you disconnect. No Stash server is involved. - **Google Drive** (Google Play services on your phone for the Google sign-in and file picker, which talk to `accounts.google.com`; then `www.googleapis.com`) — only after you add songs from Google Drive in Settings › Accounts, and only in builds that turn it on. Stash asks for `drive.file`, which lets it see only the files you pick in Google's picker and the files it saved there itself, nothing else in your Drive. It reads the start of each song you pick (a few hundred KB, sometimes its end too) for its tags and cover, checks the picked files about twice a day so trashed or edited ones are noticed, and fetches a song's bytes when you play or download it. Google sees which of your picked files are opened and your IP address. **Saving to Google Drive:** Stash creates files in your Drive only when you tap "Save to Google Drive" on a song, album or playlist, or turn on "Back up my downloads to Google Drive" (then each new download is saved). It puts them only inside a "Stash" folder it creates in My Drive (Stash › artist › album), tags them so another phone of yours, or Stash after a reinstall, recognises them instead of saving them twice, and lists that folder about twice a day so the songs show up on your other phones. It never edits or deletes your other files; the only thing it ever deletes is a copy it has just uploaded that arrived damaged. Removing a download from your phone, or disconnecting Google Drive, doesn't delete the copy in your Drive. Uploads follow the Sync tab's Wi-Fi only setting (on unless you turned it off). The access token is stored encrypted on your phone and is sent only to Google; disconnecting forgets it (you can also remove Stash's access in your Google Account settings). No Stash server is involved: the audio comes straight from Google to your phone. - **`stash-tipjar.rawnaldclark.workers.dev`** — the public supporters list behind the Home supporter pill. Fetch only; it's told nothing about you. - **The share Worker** (`stashfm.app`, and its first address, `stash-share.rawnaldclark.workers.dev`) — a Worker the project runs, behind four features. Depending on its version, Stash uses one address or the other; versions that use `stashfm.app` switch to the other address when `stashfm.app` can't be reached. - **Shared mixes** — only when you share, open or follow one. Sharing a mix sends its name, its songs (title, artist and, when known, album, length, ISRC and Spotify/YouTube IDs), up to four album-art links and, if you choose, a display name; editing it sends the new version, and deleting it sends a delete that takes the link down. Opening or following a mix reads it back; followed mixes are re-checked after syncs and on app start at most every 6 hours. Nothing about your account, device or listening is sent. The Worker uses your IP address to rate-limit and doesn't store it. Anyone with a mix's link can read it; links can't be guessed and nothing lists them. - **Song links** — only when you share a song as a Stash link or open one. A long song link carries the song's details in the link itself, so making one, or opening one in Stash, sends nothing. A short song link (`stashfm.app/t/` and a code) is stored on the Worker instead: making one sends the song's title, artist and, when known, album, length, ISRC, Spotify/YouTube IDs and album-art link, and opening one in Stash reads them back. A short link holds the song and when the link was first made, nothing about who made it, and it doesn't expire. If the Worker can't be reached when you share, Stash shares a long link instead. Anyone with a song link can read it. The Worker uses your IP address to rate-limit and doesn't store it. - **Listen Together** — starting or joining a session opens a connection to a short-lived room that sees each member's display name (if they set one), the songs played in it (the same descriptors as a shared mix), including the host's upcoming queue (up to 200 songs), sent when a session starts, and play, pause, seek, suggestion and reaction messages. Each phone also sends its sync status (ready, buffering, can't play this song, catching up) and exchanges clock pings with the room to stay in sync; a ping carries only the time since the session started, not how long your phone has been on. Room codes are 8 characters, and looking a room up is rate-limited (60 a minute per IP), so a live session can't be found by guessing codes. Your IP address is used only for rate limiting and isn't stored, and nothing is kept once the room closes (5 minutes after the last person leaves, or 12 hours after it started), apart from Cloudflare's 30-day recovery history, which only the owner could use to restore a closed room. - **Community** — only while you've turned it on in Home layout. The first time you post or vote, your phone makes a random key that identifies it; posting, voting and taking a post down send it. Home reads the list each time it shows the section, and opening a post reads that post; once your phone has a key, reads carry it, so your own votes and posts show as yours, and send nothing else about you. Posting sends the playlist's or song's details (the same descriptors as a shared mix, with album-art links) and your display name; voting sends which post and which way. Posts are public to anyone, with your display name; vote counts are public, but who voted which way isn't. The server never stores your key or IP address as they are. Rate limiting uses your IP address without keeping it. Each post and vote is kept with a scrambled form of your key (a hash), and a scrambled form of your IP address (a salted hash) used for the limits per connection; for IPv6, Community uses only the part of the address that identifies your connection. A vote (which post, which way, when) is kept until you take it back or its post is deleted. If the owner blocks your phone, a record of the block (the key's hash, when, and which post) is kept until it's lifted. A post leaves Community as soon as it's taken down, or 30 days after posting; a once-a-day cleanup then deletes it and its votes 30–31 days after posting, or 1–2 days after a take-down, whichever comes first. Anything Community deletes stays restorable by the owner from Cloudflare's always-on database backups for up to 30 days. - **The sync Worker** (`sync.stashfm.app`) — a Worker the project runs, used only after you link Stash on the web (Settings › Library & Storage › Link Stash on the web). It stores, end-to-end encrypted with a key only your linked devices have, what you chose to share between them: what's playing and your queue (when "Pick up where you left off" is on), the likes, plays and playlists you chose to mirror, and anything you send to the other device. The Worker can't read any of it; it sees sizes, times and a random id per device. Each device checks it when Stash opens and sends to it when what's playing or your mirrored library changes; while mirroring is on, the app also checks every 6 hours in the background and Stash on the web every few minutes while it's open. Logins, files, downloads and settings never go. Your IP address is used for rate limiting and isn't stored. A device that hasn't been seen for 90 days is removed, and a link with no devices left is deleted; what's playing is deleted after 7 days, and a send after 7 days or when it's received. Unlinking cuts a device off at once. Cloudflare's 30-day recovery history holds only the encrypted data. - **Google Cast** — only once you open the speaker list in Now Playing. Until then Stash only checks whether your phone has Google Play services, to show the cast icon. Opening the list starts Google's Cast library (part of Play services: Google's code, not Stash's), which looks for speakers on your Wi-Fi; until Stash is closed, it may look again now and then while Stash is open. Connecting to a speaker goes through it too, and the speaker loads Google's media receiver app from Google. Stash sends the speaker the song's title, artist, album and cover; a cover from the web goes as its link, which the speaker loads itself. The speaker gets the audio, and covers stored on your phone, from your phone: while casting, Stash runs a small web server on your local network at an address that can't be guessed, and stops it when you stop casting. Like casting from any app on a home network, that connection isn't encrypted. Streamed songs are fetched by your phone from the same hosts as normal playback. - **Album art and artist photos** — load straight from the service they come from: Spotify (`i.scdn.co`, `mosaic.scdn.co`, and `*.spotifycdn.com` hosts such as `pickasso.spotifycdn.com` for its mixes), YouTube (`i.ytimg.com`, `lh3.googleusercontent.com`, `yt3.googleusercontent.com`, `yt3.ggpht.com`), Last.fm (`lastfm.freetls.fastly.net`, `lastfm-img.freetls.fastly.net`), Qobuz (`static.qobuz.com`) and JioSaavn (`c.saavncdn.com`). A short song link can also bring a cover from Deezer (`cdn-images.dzcdn.net`, `e-cdns-images.dzcdn.net`). A playlist cover loads from whatever image address its service hands back. Each image host sees your IP address, as with any image on the web. Some of these run without being asked: - **Every time Stash starts**, with no setting to turn it off: a visitor id from `music.youtube.com` and a security check with `www.youtube.com`, so YouTube playback starts sooner, even if you never play anything from YouTube; with a connection, a warm-up request to `music.youtube.com`, the Stash and yt-dlp update checks on `api.github.com` (a newer yt-dlp is downloaded from GitHub when there is one), and a test run of yt-dlp on one public YouTube video, through `www.youtube.com`; in the official release build, the relay config, then every 6 hours while Stash runs; a fill-in for missing album art on downloaded songs (their artist and title go to Last.fm through the proxy, or Stash uses the song's YouTube thumbnail from `i.ytimg.com`); on Wi-Fi, an artist-photo lookup that sends `music.youtube.com` the name of each artist in your library that has no photo yet, which also runs after each sync; downloads you started that are still waiting are picked up again, and retried later; and, if you follow shared mixes, a check for new versions, at most every 6 hours. - **On a schedule**, with no setting to turn it off: the Stash and yt-dlp update checks, daily; once after each app update, on Wi-Fi, the lyrics upgrade pass, unless the lyrics source is set to LRC only; and once after each app update, with a connection, album art for downloaded songs whose files don't carry their tags yet, from the album-art hosts above. - **While Stash Mixes is on**, which is the default (turn it off with "Stash Mixes (beta)" in Settings › Library & Storage): at each launch and daily, the artists and songs you've played most in the last day or two, your longer-term top artists and songs, and your top genres go to Last.fm's similar-artist, similar-song and genre lookups, through the proxy; daily, on the network your download setting allows (Wi-Fi while charging, by default), the artist and title of each downloaded song that has no genre tags yet go to Last.fm, through the proxy, for its tags. Neither needs a Last.fm account, and if the proxy fails, these go to Last.fm directly. With Last.fm connected, your top artists and songs are also read from your account, and your downloaded songs are looked up daily under your username, straight from `ws.audioscrobbler.com`. - **While Lossless is on**, which is the default: with Download on, after downloads finish, a pass that looks for FLAC copies of downloaded songs that came in lossy, in Qobuz's catalog, then through your account or the relay. - **Only if you turn it on**: Auto-sync, which syncs your library on the days and at the time you pick; and scrobbling, likes mirroring, YouTube Music history, Discord Rich Presence and casting, as described above. Opening Home also loads the Qobuz rows, unless you've turned them off, and the supporters list. Two more, `qobuz.squid.wtf` and `qobuz.kennyy.com.br`, still have code in the repo but are parked: the resolve chain skips them, so a release build never calls them.