Cycling training zones
An FTP of 250 watts puts threshold intervals at 225–263 W and neuromuscular sprints from 375 W up: seven Coggan power zones, or five heart-rate zones from your lactate threshold, in whole numbers for your head unit. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Worked example
- Threshold
- 250
- Basis
- Ftp
- Model
- coggan W
- Zone count
- 7
Cycling training zones turn one threshold number into the watt or heart-rate bands a rider actually trains in. Functional threshold power, or FTP, is the highest power you can hold for about an hour; lactate-threshold heart rate, or LTHR, is the same idea measured in beats per minute. This cycling zones calculator applies the Coggan percentages used by coaches and indoor trainers: seven power zones from FTP, or five heart-rate zones from LTHR, and returns the lower and upper bound of each zone in watts or bpm.
What cycling training zones are and how Coggan percentages split a threshold
A cycling training zone is a bounded intensity band expressed as a percentage of a threshold you have already measured. For power-based training that threshold is functional threshold power, the highest mean watts a rider can sustain for roughly sixty minutes. For heart-rate training it is lactate-threshold heart rate, the beats per minute at the same sustainable effort.
Once the threshold is known, coaches do not pick arbitrary round numbers: they apply a published percentage table so Zone 2 endurance, Zone 4 threshold intervals, and Zone 5 VO2max repeats mean the same thing across a club, an indoor trainer, and a race week.
This calculator implements the Coggan model used in coaching software: for FTP it returns seven zones whose cuts sit at 55, 75, 90, 105, 120, and 150 percent of the threshold in watts, named Active Recovery, Endurance, Tempo, Lactate Threshold, VO2max, Anaerobic Capacity, and Neuromuscular Power. For LTHR it returns five zones whose cuts sit at 68, 83, 94, and 105 percent of the threshold in beats per minute, using the same names for the first five bands. Zone 1 always starts at zero.
The last zone is open-ended: neuromuscular power begins at 150 percent of FTP, and the top heart-rate zone begins at 105 percent of LTHR, with no upper bound because sprint and VO2max efforts are not capped by a percentage. Bounds are rounded to whole watts or whole beats per minute so a head unit and a printed zone card can show the same integers.
The tool does not estimate FTP from a twenty-minute test, does not apply the Karvonen reserve method from age and resting heart rate, and does not invent a threshold from body weight. You already have the threshold; this landing only splits it into training bands.
What you can do with it
Power-meter zone cards after an FTP test
Enter the new FTP in watts and print Coggan Zone 1–7 bounds for the head unit, indoor trainer, and paper card the rider actually uses.
Heart-rate zones from a lactate-threshold field test
Coaches who still prescribe by LTHR send the threshold in bpm and receive five Coggan heart-rate bands without a spreadsheet of percentages.
FAQ
How does the cycling zones calculator split FTP or LTHR?
It multiplies the positive threshold by Coggan percentages and rounds each bound to a whole watt or beat. Power uses seven zones (cuts at 55, 75, 90, 105, 120, 150 percent of FTP). Heart rate uses five zones (cuts at 68, 83, 94, 105 percent of LTHR). The last zone has no upper bound.
What happens if the threshold value is not positive?
Zero, negative, missing, and non-finite thresholds cannot define training zones, and those failures are not billed.
Is this the same as a Karvonen heart-rate zone calculator?
No. Karvonen needs age and resting heart rate and uses heart-rate reserve. This tool needs a cycling threshold you already measured (FTP or LTHR) and applies Coggan percentages.
Does this estimate FTP from a 20-minute test?
No. It only splits a threshold you already have. Estimating FTP as 95 percent of a twenty-minute average is a different calculator.
What does the cycling zones calculator cost?
The browser tool runs free on your device.
Related tools
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/sports/cycling-zones \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"threshold":250}'const res = await fetch("https://api.kit.forhosting.com/sports/cycling-zones", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"threshold": 250
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/sports/cycling-zones",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"threshold": 250
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/sports/cycling-zones", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"threshold":250}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"threshold":250}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/sports/cycling-zones", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"threshold": 250
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "sports.cycling_zones",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
Technical notes
Enter a positive threshold and choose the basis; a zero or negative value is rejected with invalid_input instead of inventing empty bands.
Input contract, response shape, and when a threshold is rejected
Basis must be ftp or lthr when present; it defaults to ftp. Values above two thousand are rejected as outside the supported range for a human FTP or LTHR. Example: threshold 250 with basis ftp yields Zone 1 from 0 to 138 watts at 55 percent, Zone 4 from 225 to 263 watts around threshold, and Zone 7 from 375 watts upward. Example: threshold 170 with basis lthr yields Zone 1 from 0 to 116 bpm and Zone 5 from 179 bpm upward. POST a JSON body with a required finite number named threshold and an optional basis. Threshold is the athlete's functional threshold power in watts or lactate-threshold heart rate in bpm; aliases ftp, ftp_watts, watts, power, lthr, threshold_hr, hr, bpm, and value are accepted so exports from head units and spreadsheets do not need a rename. If you omit basis but send only lthr or threshold_hr, the calculator infers heart-rate zones; if you send ftp or watts, it infers power zones. Threshold must be strictly greater than zero: zero watts, a negative heart rate, missing fields, empty strings, and non-finite values fail with invalid_input naming threshold, and those failures are not billed. On success the body echoes basis, threshold, unit (W or bpm), model coggan, zone_count, a zones array, and a formula string. Each zone object carries zone number, name, min_pct, optional max_pct, min, and optional max. The last zone omits max and max_pct because it is open-ended, never a null. Pair the table with your own workout builder if you need intervals; this endpoint only returns zone boundaries.
Where a cycling zones calculator earns its keep, and what it is not
Call the free in-browser cycling zones calculator for club nights, indoor-trainer setup, and coaching consults where a rider already knows FTP or LTHR and wants the Coggan card without opening a spreadsheet. Authors shipping SDKs can map the zones array into a printed card, a head-unit workout file, or an indoor ERG target without re-encoding the Coggan percentages in every language. Riders who retest FTP after a training block can resubmit the new watts and replace last season's card in one call. Keep requests small, pin the rounded min and max values in tests, and you get reproducible cycling training zones for courseware plugins, edge workers, and offline widgets without standing up a full training-load stack of your own. Use the prepaid API when a training log, bike-fit form, or fleet tool needs a stable contract under KIT balance at $0.002 per successful item; validation failures never consume balance after the engine rejects a threshold that is not positive. This landing is not a heart-rate zone calculator of the Karvonen kind: it does not take age or resting heart rate, and it does not compute 220 minus age. It is not an FTP-from-20-minute-test tool: it will not multiply a twenty-minute average by ninety-five percent. It is not a cycling calorie, speed-from-power, or gear-development calculator. Those are different search intents with their own pages.
Integration use cases
Coaching CRMs and indoor-trainer setup
Training logs call a prepaid endpoint for deterministic watt or bpm bounds instead of duplicating Coggan cuts in every language and client.