Privacy policy
Indoor Bike stores everything on your device. We don't run a server, we don't have analytics, we don't have accounts. The only time data leaves your phone is when you explicitly ask it to: uploading a ride to a third-party service you've configured, or sharing a workout file.
This page describes, in plain language, exactly what the Indoor Bike Android app and the indoorbike.app website do with data. It applies to the version of the app and site published at the date above. If anything changes materially, we'll update the date and call out the change in the project's release notes.
The Android app
What's stored on your device
- Profile values: your FTP, weight, and max heart rate, used to compute targets and zones.
- Paired-device identifiers: the Bluetooth MAC of your trainer, heart-rate monitor, and controller, so the app can reconnect on next launch without re-pairing.
- Workouts: bundled JSON shipped with the app, plus any workouts you import (file pick, share-sheet, QR, or by URL). Imported workouts live as plain JSON in the app's private storage.
- Subscribed repositories: the manifest URL and last-fetched copy of any community repository you've subscribed to.
- Ride history: the result of each ride (duration, average power / cadence / heart rate, plus a 1-second-resolution telemetry log) in a local SQLite database.
- Optional integration credentials: if you set them, your Intervals.icu athlete ID and API key, stored in the same on-device key/value store as your profile values.
All of the above lives in the app's private storage on your phone. No account, no cloud sync, no server-side copy. Uninstalling the app removes everything.
What goes over Bluetooth
The app speaks the standard Bluetooth Low Energy FTMS (Fitness Machine Service) profile to your trainer, the standard Heart Rate Service to a heart-rate monitor, and a vendor service to a Zwift Click controller where applicable. Communication is point-to-point with your devices. It doesn't go to a server.
Permissions the app requests
- Bluetooth (scan + connect): required to discover and talk to your trainer, HRM, and controller.
- Camera: only used while the in-app QR scanner is open, to read workout-import codes. Frames are processed locally and never stored.
- Notifications (Android 13+): requested on first ride so the foreground "ride in progress" notification can show.
- Location (Android 11 and earlier): the OS requires this to allow Bluetooth scans on older Android versions. We don't use location in any other way and don't store coordinates.
The app is built without Google Play Services, Firebase, Crashlytics, or any analytics SDK. The only external network connections it can make are the ones you explicitly initiate (workout / repository imports, and the optional integration described next).
Optional Intervals.icu sync
Intervals.icu is a third-party training analytics service. The integration is off by default. If you enable it in Settings → Cloud sync, the app:
- Stores the athlete ID and API key you provide on your device.
-
Uploads ride files to
intervals.icu's API when you manually tap Upload, or (if you turn on auto-upload) once per ride on completion. Each upload is a single POST containing the ride's FIT file (power, cadence, heart-rate trace). - Does not share workouts, settings, or any other app data with intervals.icu.
Once a ride is uploaded, the data is governed by Intervals.icu's own privacy policy. Disabling the integration in Settings clears the credentials and stops further uploads; rides already uploaded stay with intervals.icu unless you delete them there.
The website
indoorbike.app is a static site. There is no backend, no database, no account system, and no analytics. The site does not set first-party cookies. The bullets below describe each page's data behaviour.
Browsing the homepage and community pages
Loading any page transmits only the standard HTTP request headers your browser sends to any website (IP address, User-Agent). The static-site host's server logs may retain these for an operator-defined period to detect abuse; we don't ingest, aggregate, or analyse those logs for product purposes.
The community browser (/community/) fetches static JSON
files: /registry.json on first load, then each
repository's manifest when you click into it. These are normal HTTPS
GETs to the same site or to whichever host the repository's manifest
lives on. No data about your visit is sent to those hosts beyond what
the request itself carries.
The workout editor
The editor is fully client-side. The workout you build is saved to your
browser's localStorage under a key the editor owns
(indoorbike.editor.workout.v1) and your FTP setting under
indoorbike.editor.ftp.v1. Both stay on your device; the
editor does not transmit them anywhere. Clearing site data in your
browser removes them.
When you generate a share link or QR code, the workout is encoded into
the URL itself as a hash fragment (#wz=…). Browsers
never send the hash fragment to the server, so the receiving
page can decode the workout entirely client-side. The editor's "Send
to phone" feature uses the browser's standard share API; the file is
handed to whichever app you select. No copy goes through us.
The /share/ page
Visiting a share link with a workout in the URL means your browser decodes the workout from the hash fragment and renders it. As above, the hash fragment never leaves your browser. The "Send to Indoor Bike" button uses the browser's share API; the file is offered to your installed apps and never returns to us.
Third-party assets we load
The website pulls in a small number of static assets from public CDNs. Loading them causes your browser to make a request directly to those hosts; their privacy and logging policies apply to those connections. We don't add tracking parameters or send any custom data with the requests.
-
Google Fonts (
fonts.googleapis.com): the Bebas Neue, Inter, and JetBrains Mono typefaces. -
jsDelivr (
cdn.jsdelivr.net): two small libraries.qrcode-generator(used by the editor to draw QR codes) andpako(used by the editor, community, and share pages to gzip / un-gzip workout payloads in URLs).
We may self-host these in a future release to remove the dependency altogether.
Cookies, tracking, advertising
None. The site does not set first-party cookies. There is no advertising, no fingerprinting, no analytics, no A/B-test framework. The hosting provider may set technical cookies for protocol-level reasons (e.g. CDN routing); these are operational and not used for any product purpose by us.
Children
The app and site are not directed at children under 13. We don't knowingly collect data from anyone, of any age. See above.
Changes to this policy
Material changes will be reflected by updating the "Last updated" date at the top and called out in the app's and website's release notes. Past versions of this policy are visible in the project's git history on Codeberg.
Contact
For privacy questions or data-related requests, the fastest route is to open an issue on the project's Codeberg repository. Sensitive matters can be flagged in a private issue.