# Exercises

> The exercises a client has logged, with their personal records and every time they did one.

Source: https://hubfit.com/developers/api/exercises


The exercises a client has logged, with their best results and each time they did one.

Endpoints:

- `GET /v1/clients/{clientId}/exercises`: [List a client's exercises](https://hubfit.com/developers/api/exercises#list-client-exercises)
- `GET /v1/clients/{clientId}/exercises/{exerciseId}/history`: [List a client's history for an exercise](https://hubfit.com/developers/api/exercises#list-client-exercise-history)

## Exercise object

### Attributes

- `exerciseId` (string): The same as `exerciseId` on the exercises of a completed workout. Use it to get the exercise's history.
- `name` (string)
- `lastLoggedAt` (timestamp, nullable): When the client last logged sets for it. `null` if they never entered any.
- `personalRecords` (object): The client's best results on this exercise, in `weightUnit`. Each is `null` until the client logs a weight for it.
  - `maxWeight` (number, nullable): The heaviest set.
  - `maxVolume` (number, nullable): The most weight times reps added up over the exercise's sets in one workout.
  - `estimatedOneRepMax` (number, nullable): The highest one-rep max HubFit estimates from a set's weight and reps (the Epley formula).
- `weightUnit` (enum): The unit of every weight here, which is the client's unit setting. Weights aren't converted, the way amounts come with their currency. HubFit doesn't record the unit each workout was logged in, so a client who changed their setting may have older workouts logged in the other unit. Known values `kg`, `lb`.

### Example

```json
{
  "exerciseId": "6ad6afb311a359b211a1c6b1",
  "name": "Barbell bench press",
  "lastLoggedAt": "2026-10-26T14:32:05.000Z",
  "personalRecords": {
    "maxWeight": 205,
    "maxVolume": 4920,
    "estimatedOneRepMax": 231
  },
  "weightUnit": "lb"
}
```

## Exercise history entry object

### Attributes

- `workout` (object): The completed workout this was logged in. Pass its `id` to `GET /completed-workouts/{workoutId}` for the rest of it.
  - `id` (string, nullable): `null` if HubFit didn't record which workout it was.
  - `name` (string)
- `completedAt` (timestamp)
- `isAlternative` (boolean): Whether the client did this exercise as an alternative to the planned one.
- `note` (string): The client's note on the exercise that time. Empty if none.
- `sets` (array of objects): One set, with a value for each thing the exercise tracks, or `null` when the client left it blank. `weight` is in `weightUnit` and `durationSeconds` in seconds; other values are as the client entered them.
- `newPersonalRecords` (array of objects): The personal records the client set on the exercise that time.
  - `type` (enum): `max_weight` for the heaviest set, `max_volume` for the most weight times reps added up over the exercise's sets in one workout, `estimated_one_rep_max` for the one-rep max HubFit estimates from a set's weight and reps (the Epley formula). New record types can appear. Known values `max_weight`, `max_volume`, `estimated_one_rep_max`.
  - `value` (number): The new record, in `weightUnit`.
  - `improvementPercent` (number): How much it beat the previous record by, as a percentage. 100 when the previous record was 0, such as when earlier sessions had no weight entered (HubFit doesn't keep the previous value, so this can't be told apart from doubling it).
- `weightUnit` (enum): The unit of every weight here, which is the client's unit setting. Weights aren't converted, the way amounts come with their currency. HubFit doesn't record the unit each workout was logged in, so a client who changed their setting may have older workouts logged in the other unit. Known values `kg`, `lb`.

### Example

```json
{
  "workout": {
    "id": "6ad6af9c607c3e9d607dd19a",
    "name": "Upper body A"
  },
  "completedAt": "2026-10-26T14:32:05.000Z",
  "isAlternative": false,
  "note": "",
  "sets": [
    {
      "reps": 8,
      "weight": 185,
      "rpe": 8
    }
  ],
  "newPersonalRecords": [
    {
      "type": "max_weight",
      "value": 182.4,
      "improvementPercent": 4.2
    }
  ],
  "weightUnit": "lb"
}
```

## List a client's exercises

`GET https://api.hubfit.com/v1/clients/{clientId}/exercises`

Every exercise the client has logged, most recently logged first, with their best results. Requires `clients:read`.

### Path parameters

- `clientId` (string, required): The client's `id`.

### Query parameters

- `search` (string): Case-insensitive match on part of the exercise's name, e.g. `bench`.
- `limit` (integer): How many results to return, from 1 to 100. Default `25`.
- `cursor` (string): The `nextCursor` from the previous page.

### Returns

A page of exercises.

### Example request

cURL:

```bash
curl -G https://api.hubfit.com/v1/clients/6ad6af41d3199442d31b2743/exercises \
  -H "Authorization: Bearer $HUBFIT_API_KEY" \
  -d search=bench
```

Node.js:

```javascript
const response = await fetch("https://api.hubfit.com/v1/clients/6ad6af41d3199442d31b2743/exercises?search=bench", {
  headers: {
    Authorization: `Bearer ${process.env.HUBFIT_API_KEY}`,
  },
});

const { data, nextCursor } = await response.json();
```

Python:

```python
import os
import requests

response = requests.get(
    "https://api.hubfit.com/v1/clients/6ad6af41d3199442d31b2743/exercises",
    headers={"Authorization": f"Bearer {os.environ['HUBFIT_API_KEY']}"},
    params={
        "search": "bench",
    },
)

page = response.json()
data, next_cursor = page["data"], page["nextCursor"]
```

### Example response (200 OK)

```json
{
  "data": [
    {
      "exerciseId": "6ad6afb311a359b211a1c6b1",
      "name": "Barbell bench press",
      "lastLoggedAt": "2026-10-26T14:32:05.000Z",
      "personalRecords": {
        "maxWeight": 205,
        "maxVolume": 4920,
        "estimatedOneRepMax": 231
      },
      "weightUnit": "lb"
    }
  ],
  "nextCursor": "eyJpZCI6IjZhZDZhZjNmMjljNTk3ZjE1In0",
  "totalCount": 48
}
```

## List a client's history for an exercise

`GET https://api.hubfit.com/v1/clients/{clientId}/exercises/{exerciseId}/history`

Each time the client logged the exercise, most recent first, with the sets they did. `totalCount` is how many times they've logged it (within the dates you pass). 404 if the client has never logged it. Requires `clients:read`.

### Path parameters

- `clientId` (string, required): The client's `id`.
- `exerciseId` (string, required): An `exerciseId` from `GET /clients/{clientId}/exercises`.

### Query parameters

- `startDate` (date): Only workouts from this day, included, as `YYYY-MM-DD`, in your own calendar (the timezone in your HubFit settings), as the app shows workouts to you.
- `endDate` (date): Only workouts up to this day, included, as `YYYY-MM-DD`, in your own calendar.
- `limit` (integer): How many results to return, from 1 to 100. Default `25`.
- `cursor` (string): The `nextCursor` from the previous page.

### Returns

A page of the exercise's history.

### Example request

cURL:

```bash
curl -G https://api.hubfit.com/v1/clients/6ad6af41d3199442d31b2743/exercises/6ad6afb311a359b211a1c6b1/history \
  -H "Authorization: Bearer $HUBFIT_API_KEY" \
  -d startDate=2026-09-01
```

Node.js:

```javascript
const response = await fetch("https://api.hubfit.com/v1/clients/6ad6af41d3199442d31b2743/exercises/6ad6afb311a359b211a1c6b1/history?startDate=2026-09-01", {
  headers: {
    Authorization: `Bearer ${process.env.HUBFIT_API_KEY}`,
  },
});

const { data, nextCursor } = await response.json();
```

Python:

```python
import os
import requests

response = requests.get(
    "https://api.hubfit.com/v1/clients/6ad6af41d3199442d31b2743/exercises/6ad6afb311a359b211a1c6b1/history",
    headers={"Authorization": f"Bearer {os.environ['HUBFIT_API_KEY']}"},
    params={
        "startDate": "2026-09-01",
    },
)

page = response.json()
data, next_cursor = page["data"], page["nextCursor"]
```

### Example response (200 OK)

```json
{
  "data": [
    {
      "workout": {
        "id": "6ad6af9c607c3e9d607dd19a",
        "name": "Upper body A"
      },
      "completedAt": "2026-10-26T14:32:05.000Z",
      "isAlternative": false,
      "note": "",
      "sets": [
        {
          "reps": 8,
          "weight": 185,
          "rpe": 8
        }
      ],
      "newPersonalRecords": [
        {
          "type": "max_weight",
          "value": 182.4,
          "improvementPercent": 4.2
        }
      ],
      "weightUnit": "lb"
    }
  ],
  "nextCursor": "eyJpZCI6IjZhZDZhZjNmMjljNTk3ZjE1In0",
  "totalCount": 48
}
```
