> For the complete documentation index, see [llms.txt](https://docs.tryterra.co/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tryterra.co/planned-workouts-api/overview.md).

# Overview

## Terra Planned Workouts API

Push structured workouts directly to your users' fitness devices.

### Documentation

1. Introduction & Quick Start - Get started in 5 minutes
2. Pricing & Access - Add-on cost, credits, and what works without it
3. Core Concepts - Workout structure, blocks, steps, targets
4. Sport-Specific Examples - Running, cycling, swimming, strength
5. Athlete Parameters - Personalizing workouts per user
6. Provider Compatibility - What works where
7. Coercion Warnings - Handling feature limitations
8. Exercise Reference - Exercise names and how they map across providers
9. Garmin & Hevy Exercise Reference - Provider exercise catalogs

### Quick Links

* OpenAPI Specification (to be added soon)
* Garmin Exercise Names reference

### API Endpoints

| Method | Endpoint                          | Description                                                       |
| ------ | --------------------------------- | ----------------------------------------------------------------- |
| POST   | `/workouts`                       | Create workout template                                           |
| GET    | `/workouts`                       | List all templates                                                |
| GET    | `/workouts/{id}`                  | Get template details                                              |
| DELETE | `/workouts/{id}`                  | Delete template                                                   |
| POST   | `/workouts/{id}/plan?user_id=X`   | Schedule workout to user                                          |
| GET    | `/plannedWorkouts/{id}`           | Get a planned workout created through Terra                       |
| GET    | `/plannedWorkouts?user_id=X`      | List Terra-created and provider-created workouts (dates optional) |
| PATCH  | `/plannedWorkouts/{id}?user_id=X` | Update scheduled date (only `planned_date`, not athlete params)   |
| DELETE | `/plannedWorkouts/{id}?user_id=X` | Remove scheduled workout                                          |

A template's `estimated_tss`, `estimated_intensity_factor`, `estimated_distance_meters` and `estimated_calories` are forwarded to TrainingPeaks when you plan or reschedule the workout. TrainingPeaks ignores TSS, intensity factor and duration whenever any step target is something other than RPE: it derives them from the athlete's own thresholds instead, and Terra returns warnings on `workout.estimated_tss`, `workout.estimated_intensity_factor` and `workout.estimated_duration_seconds`. `POST /workouts` rejects an `estimated_tss` outside 0 to 9999 and an `estimated_intensity_factor` outside 0 to 5.

{% hint style="warning" %}

#### Deleting a workout template cascades to all scheduled workouts

When you call `DELETE /workouts/{id}`, Terra automatically removes all planned workouts linked to that template from every connected provider (e.g. Garmin, Suunto, Wahoo) before deleting the template itself. This cannot be undone. If you only want to unschedule a workout for a specific user, use `DELETE /plannedWorkouts/{id}` instead.
{% endhint %}

{% prompt description="Create a workout template and schedule it to a device" icon="terminal" openInAIProviders="true" defaultExpanded="full" %}

```markdown
Create a 5x1km interval workout template and schedule it onto user 8f2a1c's Garmin for tomorrow.

Use the Terra CLI: brew install tryterra/tap/terra, or npm install -g @tryterra/cli, then terra login. Run terra docs ask --question "<question>" for anything you need from the Terra docs, and terra <command> --help for usage.
```

{% endprompt %}

### Reading scheduled workouts

`GET /plannedWorkouts?user_id=X` returns everything on the user's calendar, from both origins, in one shape. Every item carries the workout body under `workout` (name, sport, `step_blocks`, estimates), plus `planned_date`, `provider_workout_id` and `warnings`:

* **Workouts you scheduled through Terra**: identified by a `planned_workout_id`. Use it with the `GET`/`PATCH`/`DELETE /plannedWorkouts/{id}` endpoints. The workout body is inline under `workout` and `workout_id` names the template it was planned from.
* **Workouts created on the provider's side** (e.g. by a coach on TrainingPeaks): marked `is_external: true`. These are read live from the provider and are read-only. `planned_workout_id` and `workout_id` are null and they cannot be addressed by id. The body arrives under `workout` like every other item: title, sport, structured steps with targets, and planned estimates such as `estimated_duration_seconds`, `estimated_tss` and `estimated_intensity_factor`. Use `provider_workout_id` as their stable identifier; a completed workout's activity carries the same id in `summary_id`.

Provider-created workouts are listed for Garmin, Wahoo, Suunto, TrainingPeaks and Intervals.icu. Intervals.icu is read-only for planned workouts: you can list what the athlete has planned there, but you cannot push to it. COROS, Zepp, Huawei and Apple have no way to read a calendar back, and Hevy has no calendar (a workout pushed to Hevy becomes a library routine and carries no date), so for those providers the list contains only the workouts you scheduled through Terra. A workout you pushed through Terra that the provider echoes back is listed once, as the Terra row.

```json
{
  "planned_workout_id": null,
  "workout_id": null,
  "planned_date": "2026-07-15",
  "provider_workout_id": "3841172642",
  "is_external": true,
  "created_at": null,
  "last_updated_at": null,
  "athlete_metrics": null,
  "warnings": [],
  "workout": {
    "name": "threshold",
    "sport": "cycling",
    "estimated_duration_seconds": 3816,
    "estimated_tss": 65.4,
    "estimated_intensity_factor": 0.75,
    "step_blocks": [
      {
        "completion_condition": null,
        "steps": [
          {
            "intensity_type": "warmup",
            "completion_condition": { "type": "time", "value": 900 },
            "intensity_targets": [
              { "target_type": "heart_rate_max_percentage", "value_low": 40, "value_high": 50 }
            ],
            "notes": "Warm up"
          }
        ]
      }
    ]
  }
}
```

Items also carry `coercion_warnings` and `details`, the older forms of `warnings` and `workout`. Both are deprecated: read `warnings` and `workout`, which use the same field names and values as `GET /workouts/{workout_id}`.

Two response headers report the live provider read:

| Header                            | Values                                                                              |
| --------------------------------- | ----------------------------------------------------------------------------------- |
| `X-Terra-Provider-Fetch`          | `ok`, `skipped`, `rate_limited`, `unauthorized`, `unavailable`, `timeout`, `failed` |
| `X-Terra-Provider-Data-Truncated` | `true` when provider-side workouts could not be included                            |

`skipped` means the provider does not expose planned workouts, or the Planned Workouts add-on is not active. Anything other than `ok` also sets `X-Terra-Provider-Data-Truncated: true`. The call still returns `200` with the workouts Terra has stored, so check the headers before treating a short list as an empty calendar.

`warnings` is an array of `{path, message}` entries describing what had to change between your template and the provider's format: on workouts you pushed, what the push adjusted; on provider-created workouts, what reading them back adjusted.

{% hint style="info" %}

#### Pass a date window to see upcoming workouts

Without `start_date`/`end_date`, provider-side workouts default to the **trailing 30 days**, so upcoming ones will not appear. Workouts you scheduled through Terra are not windowed when the dates are omitted. Pass an explicit window (e.g. today through next week) to list what is ahead: both bounds are planned dates and both are inclusive. Some providers also limit how far ahead they expose planned workouts.

A planned workout the user has **completed** leaves this list and is delivered as a completed activity instead.
{% endhint %}
