Indoor cycling · Android · GPLv3

Train by power.
Owe nobody anything.

Structured ERG workouts for your smart trainer. Build them in your browser, beam them to your phone with a QR code, ride them against your FTP. Everything stays on your device.

  • Free forever
  • No account
  • No tracking
  • No subscription
  • Open source

The whole idea

Browser to sweat in fifteen seconds.

The workout editor runs right here on this site - no install, no sign-up. Draw your intervals, point your phone at a QR code, and the app takes over your trainer. That's the entire pipeline.

01 · In your browser

Build

Lay out intervals as coloured blocks in the editor. Durations, %FTP, repeats - tweak until the shape feels right. Works on any laptop or tablet.

02 · One tap in the app

Beam

Tap "Send to phone" in the editor, scan the QR straight from the app's home screen. The workout lands in your library and opens, ready to start. No cloud in between.

03 · On the trainer

Ride

ERG mode drives the trainer to each interval's wattage. You hold the cadence, it holds the watts. The finished ride saves to your phone as a file - export FIT any time.

What's inside

Built for the saddle, not the screen.

Every screen is designed for arm's-length legibility on a sweaty phone. Telemetry first, decoration last.

BLE smart trainer

Standard FTMS over Bluetooth LE. Pair your Wahoo Kickr Core or any FTMS-compatible trainer. No vendor SDK, no Google Play Services.

ERG mode control

The app drives your trainer to hit each interval's target wattage automatically. You hold the cadence, it holds the watts.

Power zones

Z1 recovery through Z7 neuromuscular, calibrated as percentages of your FTP. Visualised on every workout, every interval, every chart.

JSON workouts

Every workout is a readable JSON file. Use the bundled library, drop in your own, hand-craft them in a text editor, or build them visually with the workout editor.

Zwift Click ready

Pair a Zwift Click v1 controller to nudge target power up or down mid-interval. On-screen +/- buttons work too. Your handlebar, your call.

Yours, locally

Rides, FTP, weight, paired devices: all on your phone. No telemetry leaves the device. Optional sync is opt-in and lazily loaded.

Workout library

Familiar formats. Honest names.

Sweet Spot, Threshold, VO2. The same intervals you already know from other training apps, structured against your FTP and ridden in the same colour-coded zones.

Threshold

Sweet Spot 3×12

52 MIN · TSS 65 · IF 0.91

225avg watts
92avg rpm
3intervals

VO2 Max

30/30s · 8 sets

38 MIN · TSS 58 · IF 0.94

118%FTP peak
8sets
30son/off
Z1 Recovery Z2 Endurance Z3 Tempo Z4 Threshold Z5 VO2 Z6 Anaerobic Z7 Neuro

Community libraries

Anyone can publish a library.
Including you.

A workout library is just static files: a small manifest.json plus the workouts it points at. Host it on any web space - a Codeberg or GitHub Pages site, your blog, a folder on your NAS. No server code, no gatekeeper, no permission needed.

You publish

{
  "schemaVersion": 2,
  "id": "gravel-base",
  "name": "Gravel Base Builder",
  "author": "Your Name",
  "bundles": [
    { "id": "base",
      "version": 3,
      "url": "base.json" }
  ]
}

manifest.json · anywhere HTTPS

Riders add it

  • Paste the URL in the app
  • Share it as a QR code
  • Get listed in the in-app registry

browse the pack, pick workouts

They own their copy

Every workout a rider adds becomes a local file on their phone - theirs to ride, rename, or edit. If your library changes later, nothing breaks and nothing is silently swapped underneath them.

local-first, always

Coaches, clubs, tinkerers

Publish your winter plan for the whole club. Ship a pack alongside your coaching. Curate the intervals you swear by. The in-app Browse community screen lists public libraries from the registry at indoorbike.app/registry.json - open a pull request to get yours listed, or share your manifest URL directly and skip the registry entirely.

Open API

An API with no API keys.

Third-party tools - training planners, AI coaches, analytics apps - can generate a workout and hand it to a rider as a plain URL. The link opens the editor pre-loaded; the rider tweaks it and beams it to their phone. No backend, no OAuth, no callback. No user data ever crosses a wire.

One URL is the whole integration:

https://indoorbike.app/editor/?name=Threshold+3x8&ftp=250
       &s=10m@50,3x(8m@100;4m@55),5m@50

Durations, %FTP targets, and repeat groups in a compact text form. Open this example in the editor →

Stateless by design

The workout travels inside the URL itself. Your tool never talks to our servers, and we never see your users.

Human in the loop

Links open the editor, not the phone. The rider reviews the shape, adjusts if they like, then sends it on - nothing lands on a device unseen.

From install to first sprint

Four steps. No accounts.

Install the APK, pair your trainer, pick a workout, ride. No sign-up screens, no subscription paywalls, no welcome carousels.

Install

Get the app via Obtainium (or sideload the APK from a release). Open it. The first screen asks for your FTP and weight. That's it.

Pair

Tap the trainer chip. Power on your Kickr Core, the app discovers it over BLE, and remembers it. Optionally pair a HRM and a Zwift Click.

Pick

The home screen proposes your next ride - resume, ride again, or a starred favourite. Or search the library by how much time you have.

Ride

Hit start. ERG holds the target wattage. The HUD shows zone, power, cadence, HR, and time-in-step. The completed ride saves locally as a file.

The deal

Free. No asterisk.

No price, no premium tier, no ads, no "free trial". And because it ships through F-Droid, that promise is structural: no proprietary blobs, no Google Play Services, no analytics SDKs, no crash reporters phoning home. Even this page loads zero third-party resources - the fonts are self-hosted.

  • Local-first storage. Your FTP, weight, paired devices, and ride history live on your phone.
  • No account, ever. The app is fully functional offline with zero sign-up. Optional sync exists, off by default.
  • No Google services. Standard Android BLE APIs. No FCM, no Firebase, no Crashlytics.
  • Open source, GPLv3. Kotlin Multiplatform core and Jetpack Compose UI. Read it, fork it, build it yourself.
  • Open file formats. Workouts are JSON. Saved rides are files you can copy off the device any time - FIT export included.

Read the full privacy policy →

Questions

Good to know.

Which trainers are supported?

Any smart trainer that speaks the standard Bluetooth LE FTMS profile. The first-class target is the Wahoo Kickr Core; other FTMS-compatible trainers should work but aren't extensively tested yet.

Do I need an account?

No. The app is fully functional offline with no sign-up. Optional Auth0 login and CleverCloud sync exist for users who want cross-device backup, but everything works without them.

Where do I get the app?

Install via Obtainium to get signed APKs and automatic updates straight from the Codeberg releases. An F-Droid listing is in preparation. You can also build from source.

Can I use my own workouts?

Yes. Workouts are plain JSON files describing a sequence of intervals (duration + power as % of FTP). Build them visually in the browser editor and QR them to the app, import a file, or paste a URL.

How do I publish my own workout library?

Write a small manifest.json pointing at your workout files and host both on any HTTPS web space - static hosting is enough. Riders add your library by URL or QR, and you can open a pull request to appear in the in-app Browse list. See the publishing guide.

Can my app or tool integrate with Indoor Bike?

Yes - generate an editor URL with the workout encoded in its parameters and show it to your user. No registration, no keys, no server-to-server calls. Start with the API quick start or the machine-readable spec.

Is iOS supported?

Not yet. The shared core is built with Kotlin Multiplatform Mobile so an iOS port is planned, but the current focus is shipping a polished Android release first.

What about heart-rate monitors?

Standard BLE HRMs (including the Garmin HRM) pair the same way as the trainer. HR shows on the ride HUD and is recorded with the ride file.

Ready when you are.

Install with Obtainium to get the app and automatic updates straight from our Codeberg releases - no store account, no Google services. An F-Droid release is also in preparation.

New to Obtainium? Install it, tap Add App, and paste https://codeberg.org/juhanilehtimaki/IndoorCyclingApp.