# Indoor Bike - Editor URL Workout API (v1) > Generate a link that opens https://indoorbike.app/editor/ with a structured > power-zone cycling workout already loaded. Pure URL query params. No backend, > no auth, no SDK, no compression. Build the string, output the link. Human page: https://indoorbike.app/api/ (concise, for people) Full docs: https://codefloe.com/SnappTechnology/IndoorCyclingApp/src/branch/main/docs/EDITOR_URL_API.md Badges: https://indoorbike.app/badges/ (optional, free "Open in Indoor Bike" SVG badges) This file is the authoritative machine spec. If it conflicts with anything, this wins. ## Endpoint GET (open in a browser): https://indoorbike.app/editor/? The whole workout lives in the query string. `s` is the only required param. ## Parameters s REQUIRED. The segments (the workout itself). See GRAMMAR. name optional. Workout title. Max 80 chars. desc optional. Description. Max 600 chars. (alias: description) tags optional. Comma-separated labels, e.g. tags=Threshold,Sweet Spot ftp optional. Rider FTP in watts, 50..600. Only drives the watt preview; the workout stays FTP-relative. Omit if unknown. v optional. API version. Use 1. Unknown values ignored. URL-encode values: space -> + or %20; percent-encode literal & # % inside a value. The segment syntax characters @ , ; ( ) x are URL-safe - do NOT encode them. ## GRAMMAR (the `s` param) s := item ("," item)* item := segment | repeat segment := DURATION "@" POWER [CADENCE] repeat := COUNT "x" "(" segment (";" segment)* ")" ; group repeat | COUNT "x" segment ; single-segment repeat DURATION := NUMBER ; seconds (e.g. 300) | NUMBER "s" ; seconds (e.g. 90s) | NUMBER "m" ; minutes, decimals ok (e.g. 5m, 2.5m) POWER := integer percent of FTP, 0..300 ; 100 = FTP CADENCE := "r" RPM ; single target (e.g. r90) | "r" RPM "-" RPM ; range (e.g. r85-95) RPM := integer 20..200 COUNT := integer 1..40 Top-level items are comma-separated. Segments INSIDE a repeat group are semicolon-separated (commas are reserved for the top level). CADENCE is optional and independent of POWER. The trainer holds the power in ERG mode regardless; a cadence target only tells the rider how to spin while it does (low-cadence climbing work, high-cadence leg speed). Omit it unless the workout genuinely calls for a particular cadence. ## LIMITS / VALIDATION (applied by the editor) - max 200 segments (extra dropped) - duration clamped 1s..4h, power clamped 0..300, count clamped 1..40 - cadence must be 20..200 rpm; out-of-range or malformed cadence drops the cadence target but KEEPS the segment. A reversed range (r95-85) is read as 85-95. - unparseable items are skipped silently; if `s` yields 0 valid segments the editor shows an error and opens empty - so emit valid syntax. - no per-segment labels (editor stores duration + power + optional cadence). ## EXAMPLES 3x8 threshold (warmup, 3 blocks of 8min@100% / 4min@55%, cooldown): https://indoorbike.app/editor/?v=1&name=Threshold+3x8&ftp=250&s=10m@50,3x(8m@100;4m@55),5m@50 4x10 sweet spot: https://indoorbike.app/editor/?name=Sweet+Spot+4x10&ftp=230&s=10m@55,4x(10m@90;5m@60),8m@50 6x3 VO2 max: https://indoorbike.app/editor/?name=VO2+6x3&tags=VO2+Max&s=12m@55,6x(3m@115;3m@50),8m@50 8x30s sprints: https://indoorbike.app/editor/?name=Sprints&s=10m@50,8x30s@130,5m@50 Flat list, seconds, no repeats: https://indoorbike.app/editor/?name=Pyramid&s=300@50,300@70,300@90,300@105,300@90,300@70,300@50 Cadence targets - 45min climbing block at 70-80 rpm, then leg speed at 105 rpm: https://indoorbike.app/editor/?name=Climb+%2B+Spin&ftp=230&s=10m@55,45m@78r70-80,5m@45,4x(2m@70r105;2m@50),5m@45 ## RESULT MODEL (what the link decodes to) { "schemaVersion": 1, "name": "...", "description": "...", "tags": ["..."], "segments": [ { "lengthInSeconds": 600, "powerPercentFTP": 50, "intervalType": "CONSTANT" } ] } With cadence targets, schemaVersion becomes 2 and the segments that carry one gain cadenceMinRpm / cadenceMaxRpm (a single target sets only cadenceMinRpm): { "schemaVersion": 2, "name": "...", "segments": [ { "lengthInSeconds": 2700, "powerPercentFTP": 78, "intervalType": "CONSTANT", "cadenceMinRpm": 70, "cadenceMaxRpm": 80 } ] } schemaVersion is 1 whenever no segment carries a cadence target, so plain power workouts stay loadable by app builds that predate cadence targets. ## DOWNSTREAM FLOW (for context, not needed to build the link) Opening the link renders the workout in the editor. The user then taps "Show QR & link" or "Share to phone" to import it into the Indoor Bike Android app (QR scan / OS share sheet). The third party's job ends at producing the URL.