API access to Intervals.icu

I am assuming you are using this endpoint to get planned workouts: GET /api/v1/athlete/{id}/events{format}

You can specify resolve=true to get actual watts, bpm and m/s values for power, hr and pace steps. These take into account the sport and the athlete’s settings for that sport at that time. This option was only added earlier this year.

I am actualy using default one…without {format}

GET /api/v1/athlete/{id}/events

But the thing is that I would need everything in % of the reference value(reference value beeing the value that all the workout intervals are referenced by; FTP, LTHR, MaxHR, Threshold Pace, …). So if the reference value is ftp, then I would like to get everything in %FTP, not in exact W, the same for pace, % of Threshold speed. So I can calculate it manualy by using athlete profile and their settings for this values, but that way I need to do 2 API calls.

It would be nice to always get reference value for that sport type with the actual activity so it is always up to date.

If you specify resolve=true then the FTP, LTHR or threshold pace used to convert the workout steps into watts, bpm or m/s are included in the workout_doc. These are the athlete’s current settings for the sport.

"workout_doc": {
    "description": "A ride done with power",
    "duration": 3120,
    "distance": 0.0,
    "ftp": 290,
    "lthr": 171,
    "target": "POWER",

A run done with pace:

"workout_doc": {
    "duration": 3600,
    "distance": 8310.0,
    "lthr": 171,
    "threshold_pace": 2.7777777,
    "pace_units": "MINS_KM",
    "target": "PACE",
    "steps": [

I see,

Thank you for the explanation. It all make sense now!

1 Like

Hi, first a HUGE thanks for providing such a good API. It’s so nice to be able to easily dive into my own data :slight_smile: One of the things that pushed me to this was accidentally plugging my secondary device (bike computer) into my mac while Garmin express was running. So I got a huge number of duplicated activities. Garmin support not really helpful here :man_shrugging:, and restrict API access to ‘enterprise’ developers. Strava don’t allow activity delete via API :person_shrugging: but did a selective bulk delete for me. But your API allowed me to easily identify and delete the activities pretty easily. Thanks!

One observation, and a question…

/api/v1/athlete/{id}/activities is fantastic. But I kinda flinch when giving it a huge date range, especially as there’s a fair amount of data returned per activity. I wondered if you might want to return a list of activity summaries from this endpoint, and the user can then request details on those specific activities they want it for. Also, maybe allowing the list to be filtered server side could help here (e.g. If I only want runs >10km). All of that is making me think about GraphQL, which would be very cool, but obviously non-trivial :slight_smile:

Here’s my question! I’m looking to be able to grab the best efforts of given distances (1km, 1mi, 5km, 10km, HM, Marathon) from a given activity. I don’t quite understand the ‘best-efforts’ endpoint, could you point me to an explanation?

Thanks again for a great product. This has really increased my interest and appreciation for Intervals.icu.

Stuart

The ‘Best Efforts’ implementation on the app goes like this:

  • Set up the date ranges for which you want Best Efforts results. Power curves on the Activity Power page, or similar Pace Curves on the Activity Pace page
  • Then define the Time ranges for Power or Distance ranges for Pace you’re interested in from the Best Efforts link

The table now displays all those results for the different time ranges + specifically for the opened activity ‘This Ride/run…’
You can do the exact same thing for the overall Power and Pace page.
I’m pretty sure that the end-point from the API returns either the activity table or the overall table or probably even both.

Thanks!

Yes filtering for the list activities endpoint is probably something I should do. A lot of the other endpoints already support filtering.

You need to use GET /api/v1/athlete/{id}/activity-pace-curves{ext} for your best efforts query.

You can seen it in action using the inspector by adding a “Best pace” custom chart:

GET /api/athlete/2060784/activity-pace-curves?oldest=2023-06-15T00:00:00&newest=2024-06-03T23:59:59&distances=5000&gap=false

Just change /api to /api/v1 to call it yourself. You can specify multiple distances with commas. That endpoint also supports filtering. Add some filters to the tab to see how to do it.

For one single activity you can fetch its pace curve with GET /api/v1/activity/{id}/pace-curve{ext}

1 Like

@david let me know if i’m doing something wrong. I’m trying to improve the workout library sync.

For workouts which I created by myself (either created by hand or dragging existing workouts to the library folder), I have no issues using the API to download the JSON it.

basically sending a GET to
let url = “https://intervals.icu/api/v1/athlete/\(athleteId)/workouts/\(workout.id)

then POST to
https://intervals.icu/api/v1/athlete/\(athleteId)/download-workoutjson

However, when i copy any workouts that is available from the public workout library into my own Library,

eg: Gerries Workout

Doing the same process, it will fail with the error:

Download Error: requestError[Error Domain=OAuthSwiftError Code=400 "Required request body is missing" UserInfo={NSLocalizedDescription=Required request body is missing, Response-Headers={
    "Access-Control-Allow-Origin" = "*";
    "Content-Length" = 57;
    "Content-Type" = "application/json;charset=UTF-8";
    Date = "Fri, 07 Jun 2024 13:03:29 GMT";
    "Strict-Transport-Security" = "max-age=15724800; includeSubDomains";
}, OAuthSwiftError.response=<NSHTTPURLResponse: 0x6000037f99c0> { URL: https://intervals.icu/api/v1/athlete/{athleteId}/download-workoutjson } { Status Code: 400, Headers {
    "Access-Control-Allow-Origin" =     (
        "*"
    );
    "Content-Length" =     (
        57
    );
    "Content-Type" =     (
        "application/json;charset=UTF-8"
    );
    Date =     (
        "Fri, 07 Jun 2024 13:03:29 GMT"
    );
    "Strict-Transport-Security" =     (
        "max-age=15724800; includeSubDomains"
    );
} }, OAuthSwiftError.response.data={length = 57, bytes = 0x7b227374 61747573 223a3430 302c2265 ... 69737369 6e67227d }, NSErrorFailingURLKey=https://intervals.icu/api/v1/athlete/{athleteId}/download-workoutjson, Response-Body={"status":400,"error":"Required request body is missing"}}]

when issuing a GET to one of my own libraries, i will get a response which can then be converted to JSON. But for the shared libraries, copied to my own library, nothing gets returned.

Hmm. I wasn’t able to reproduce this. The Code=400 "Required request body is missing" response says that you didn’t supply the workout doc etc. in the body of the post so maybe the GET failed?

Note that unfortunately library workout IDs are still “per athlete” (something I should have fixed long ago). So if you are fetching a workout from your library that has been shared by someone else you need to use their athlete id.

So I have this one in my library (my athlete ID is 2049151):

        {
            "athlete_id": "5039599",
            "id": 55,
            "icu_training_load": 28,
            "name": "Recovery Ride",

To fetch it I need to do:

GET /api/v1/athlete/5039599/workouts/55

To convert to JSON format for myself:

POST /api/v1/athlete/2049151/download-workout.json
(body returned by the GET above)

Tx for the tip.
Seems to be working now…

1 Like

Hi!,

Long time lurker and first time poster. I am trying to create a sync between a service with no integrations and intervals.icu so It can update my Coros Watch. But I think I am having troubles to get the excersise correct using the api. the workouts sync correctly to cores but The graph of the workout never shows on the calendar. This the json I send to the api

{
  "start_date_local": "2024-06-23T08:00:00",
  "name": "Day 1 - Long Run",
  "description": null,
  "icu_distance_target": 14960.999999999904,
  "icu_training_load": 73.20785881093873,
  "moving_time": 5100,
  "workout_doc": {
    "steps": [
      {
        "reps": 1,
        "text": "1X",
        "steps": [
          {
            "duration": 600,
            "power": {
              "end": 80,
              "start": 65,
              "units": "%ftp"
            }
          }
        ]
      },
      {
        "reps": 1,
        "text": "1X",
        "steps": [
          {
            "duration": 3000,
            "power": {
              "end": 84,
              "start": 77,
              "units": "%ftp"
            }
          }
        ]
      },
      {
        "reps": 6,
        "text": "6X",
        "steps": [
          {
            "duration": 60,
            "power": {
              "end": 107,
              "start": 102,
              "units": "%ftp"
            }
          },
          {
            "duration": 120,
            "power": {
              "end": 80,
              "start": 65,
              "units": "%ftp"
            }
          }
        ]
      },
      {
        "reps": 1,
        "text": "1X",
        "steps": [
          {
            "duration": 420,
            "power": {
              "end": 80,
              "start": 65,
              "units": "%ftp"
            }
          }
        ]
      }
    ],
    "duration": 5100,
    "target": "POWER",
    "zone_times": [
      {
        "secs": 1740,
        "id": "Z1"
      },
      {
        "secs": 3000,
        "id": "Z2"
      },
      {
        "secs": 0,
        "id": "Z3"
      },
      {
        "secs": 360,
        "id": "Z4"
      },
      {
        "secs": 0,
        "id": "Z5"
      }
    ]
  }
}

You need to supply the workout description in Intervals.icu format (as you would enter it in the workout builder) or a workout file (fit, zwo etc.). You can’t supply a workout_doc.

{
  "category": "WORKOUT",
  "start_date_local": "2024-03-30T00:00:00",
  "description": "- 2h Z2 Pace\n- 10m Z1 Pace"
}
{
  "category": "WORKOUT",
  "start_date_local": "2024-03-30T00:00:00",
  "filename": "Woddle_Toddle.fit",
  "file_contents_base64": "DiCNCG8AAAAuRklUKz9AAAEAAAMBAoQAAQAIDgcAAP8FSW50ZXJ2YWxzLmljdQBAAAEAGgMEAQAIDgcGAoQAAVdvZGRsZSBUb2RkbGUAAAFAAAEAGwcBAQACBIYDAQAFBIYGBIYHAQD+AoQAAAA27oABAAAA+AAAAQAEAAAe/A=="
}

Intervals.icu will calculate moving_time and other fields.

2 Likes

Hi David, thank you for the update. I based my json on the following post API access to Intervals.icu - #129 by Rohan_EnduroCo and if i follow this json file the changes are reflected also on the calendar. Is this because of the null values in the icu_ parameters? Thank you for the assist on this topic

In a clear view with a gpx file:

curl -X POST “https://intervals.icu/api/v1/athlete/i11111/activities?name=Prueba&description=Borrar” -H “accept: /” -H “authorization: Basic xxxxxxxxx” -F file=@c:\temp\pru.gpx

I´m having troubles whe loading from my app; I informe type=Ride but always create a type=Run activity.

curl -X POST “https://intervals.icu/api/v1/athlete/i111111/activities?name=Prueba&description=Borrar&type=Ride” -u “API_KEY:cucurrucucu” -H “accept: /” -H “accept: /” -F type=Ride -F file=@/temp/31yLg2vkjHKMSG.gpx

You cannot specify an activity type with that endpoint. The type is derived from the information in the uploaded file. For a gpx:

 <trk>
  <name>Morning Ride</name>
  <type>Ride</type>
1 Like

hey. i was looking for a way to upload a multiple amount of workouts in bulk using the api. i wondered, if there is a type of format we can create using the multiple ai tools that are out there, to do this kind of job. for example, google calendar can use a csv table to upload multiple events at once. is there something similar? tried to use the api but couldnt seem to do it properly. if so, please specify the best way to do so. thanks in advance.

you can upload in bulk using the API endPoint by sending it workout files in the format of ZWO/FIT/etc.

You need to use this endpoint to upload in bulk: https://intervals.icu/api-docs.html#post-/api/v1/athlete/-id-/events/bulk

POST /api/v1/athlete/{id}/events/bulk

It accepts a JSON array of event objects.

[{
  "category": "WORKOUT",
  "start_date_local": "2024-03-30T00:00:00",
  "filename": "Woddle_Toddle.fit",
  "file_contents_base64": "DiCNCG8AAAAuRklUKz9AAAEAAAMBAoQAAQAIDgcAAP8FSW50ZXJ2YWxzLmljdQBAAAEAGgMEAQAIDgcGAoQAAVdvZGRsZSBUb2RkbGUAAAFAAAEAGwcBAQACBIYDAQAFBIYGBIYHAQD+AoQAAAA27oABAAAA+AAAAQAEAAAe/A==",
  "external_id": "1234"
},
{
  "category": "WORKOUT",
  "start_date_local": "2024-04-01T00:00:00",
  "filename": "Woddle_Toddle2.fit",
  "file_contents_base64": "DiCNCG8AAAAuRklUKz9AAAEAAAMBAoQAAQAIDgcAAP8FSW50ZXJ2YWxzLmljdQBAAAEAGgMEAQAIDgcGAoQAAVdvZGRsZSBUb2RkbGUAAAFAAAEAGwcBAQACBIYDAQAFBIYGBIYHAQD+AoQAAAA27oABAAAA+AAAAQAEAAAe/A==",
  "external_id": "1235"
},
{
    "category": "NOTE",
    "start_date_local": "2024-04-01T00:00:00",
    "name": "Build",
    "description": "Start of build phase",
    "color": "green",
    "external_id": "1236"
}]

Here I have supplied fit files. But you can also use the native Intervals.icu format (same as workout builder) in the description field. Then you also need to supply “type” (Ride, Run etc.).

If you pass a query parameter upsert=true and are calling from an OAuth client (not using API key) then events with matching external_id are updated if they already exist.

how can i upload a plan from a csv file?
i have everything calculated in tss points?

thank you!