---
title: "Critical speed from recent hard runs, or why there isn't one"
method: GET
path: "/training/critical-speed"
tags: ["Training"]
---

# Critical speed from recent hard runs, or why there isn't one

`GET /training/critical-speed`

## Request

- Base URL: `https://bodylab.lol/api/v1`
- URL: `https://bodylab.lol/api/v1/training/critical-speed`
- Auth: HTTP bearer

Recomputed on each call from the athlete's runs. A record only: D′, the finite distance above critical speed, is used to qualify the fit and is not returned.

## Headers

- `X-Client-Platform` string
- `X-Client-Version` string

## Response `200`

OK

- object: Critical speed: the boundary between running hard that can be sustained for a long time and running hard that can't, worked out from at least three of the athlete's own hardest efforts of 3 to 15 minutes in the window, each from a different road or track run (not trail or treadmill; a critical speed test's two hard efforts count separately), grade-adjusted and normalised for altitude. A change under 5% between two fits is within what GPS alone can move it.
  - `windowDays` integer, required: How far back efforts count.
  - `fit` object, nullable, required: Null when no fit qualifies; `refusal` then says why.
    - `seaLevel` object, required: Critical speed with every effort normalised to sea level.
      - `csMs` number, required: Critical speed, metres per second.
      - `plusMinusMs` number, required: One standard error, propagated from an assumed 3% GPS distance error per effort. Show it with the number: GPS, not the athlete, sets it.
    - `today` object, nullable, required: Critical speed where the athlete is today: the sea-level figure brought to today's altitude, and never faster than at the lowest effort's altitude. Null when Body Lab doesn't know today's altitude; the sea-level figure is then the only one, and it is faster than the athlete would run at altitude.
      - `csMs` number, required: Critical speed, metres per second.
      - `plusMinusMs` number, required: One standard error, propagated from an assumed 3% GPS distance error per effort. Show it with the number: GPS, not the athlete, sets it.
      - `altitudeM` integer, required: The altitude today's figure is for, metres.
      - `altitudeBasis` string, required: Where that altitude came from. Known values: confirmed, recent_recordings, home. Treat unknown values as "other".
    - `longestS` integer, required: The longest effort's duration, which sets the answer more than any other.
  - `refusal` object, nullable, required
    - `reason` string, required: The first rule the efforts failed. too_few_efforts: fewer than three runs held a best (with eligibleRuns above the effort count, the other runs were slower at every duration). no_long_effort: no steady hard effort of 12 minutes or more. no_short_effort: none of 5 minutes or less. efforts_disagree: the efforts don't lie on one line within GPS error. d_prime_too_small, d_prime_too_large, cs_implausible: the efforts fit a line no maximal efforts would, usually because a short one wasn't all-out. Known values: too_few_efforts, no_long_effort, no_short_effort, efforts_disagree, d_prime_too_small, d_prime_too_large, cs_implausible. Treat unknown values as "other".
    - `eligibleRuns` integer, required: Runs in the window that could have counted, before choosing among them.
  - `efforts` object[], required: The efforts chosen, shortest first: the ones fitted, or with a refusal the ones that would have been.
    - `activityId` string, required: Opaque identifier.
    - `date` string, date, required: A day on the athlete's own calendar: YYYY-MM-DD, no time zone.
    - `durationS` integer, required: The window of the run that was used: one per run, the longest it held as the fastest of any run.
    - `speedMs` number, required: Grade-adjusted metres per second over that window, at the altitude it was run.
    - `speedSeaLevelMs` number, required: The same, normalised to sea level, which is what was fitted.
    - `altitudeM` integer, required: The run's median altitude, metres.
  - `deviceThreshold` object, nullable, required: The latest threshold speed a device or service reported, for comparison only: it is a lactate-threshold estimate by an undisclosed method at an unknown altitude, not interchangeable with critical speed, and Body Lab never sets a pace from it. Null when there is none.
    - `speedMs` number, required: Metres per second.
    - `on` string, date, required: A day on the athlete's own calendar: YYYY-MM-DD, no time zone.
    - `source` string, nullable, required: The connected service it came from, by id; null when unrecorded.

## Other responses

- `401`: Error: UNAUTHORIZED.

---

[API](https://skmtc.dev/bodylab/apis/body-lab-api.md) · [All operations](https://skmtc.dev/bodylab/apis/body-lab-api/llms.txt) · [OpenAPI document](https://skmtc.dev/bodylab/apis/body-lab-api/revisions/544c201eaf88?raw)
