# Intervals.icu API Integration Cookbook

**URL:** <https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090>\
**Category:** Guide\
**Created:** [20 November 2024 10:25 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090 "2024-11-20T10:25:24Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![david](https://forum.intervals.icu/user_avatar/forum.intervals.icu/david/32/30189_2.png) [@david](https://forum.intervals.icu/u/david)\
**Post date:** [20 November 2024 10:25 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090/1 "2024-11-20T10:25:24Z")

</div>

This tells you everything you need to know to get completed activities into and out of Intervals.icu, to download planned workouts and to read and update wellness data. Example calls use curl so you can easily try them out.

Sources:

- [API access to Intervals.icu](https://forum.intervals.icu/t/api-access-to-intervals-icu/609)
- [Intervals.icu OAuth support](https://forum.intervals.icu/t/intervals-icu-oauth-support/2759)
- [Intervals.icu API docs](https://intervals.icu/api-docs.html)

By using the API you agree to the [Intervals.icu API Terms and Conditions](https://forum.intervals.icu/t/intervals-icu-api-terms-and-conditions/114087) .

## Authorization

Apps need to use an access\_token obtained via [OAuth](https://forum.intervals.icu/t/intervals-icu-oauth-support/2759). If you are just working with your own data call the API using your personal API key (available towards the bottom of the [settings](https://intervals.icu/settings) page) and basic authentication.

## Uploading completed activities

This is done by [POSTing multipart/form-data](https://intervals.icu/api-docs.html#post-/api/v1/athlete/-id-/activities). Example using an access\_token from OAuth (ACTIVITY:WRITE scope is required):

```auto
$ curl -F file=@i55610271.fit \
    'https://intervals.icu/api/v1/athlete/0/activities?name=Foo&description=Bar' \
    -H 'Authorization: Bearer d842c1fc25f241e5ae440d09756448a9'

```

Same example using a personal API key:

```auto
$ curl -F file=@i55610271.fit \
    'https://intervals.icu/api/v1/athlete/0/activities?name=Foo&description=Bar' \
    -u API_KEY:6dzmsans11bsyifjxchr87oum

```

The name and description URL parameters are optional. The file can be fit, tcx or gpx or a zip or gz of the same. There is also an optional external\_id parameter which you can set to your own activity ID. You can configure an activity URL template for your application and Intervals.icu will use this and the external\_id to link back to your application from the activity pages.

Note that the athlete id in the path is ‘0’. This indicates that the athlete ID that the access\_token or API key belongs to should be used.

## Downloading planned workouts

See [Downloading planned workouts from the API](https://forum.intervals.icu/t/downloading-planned-workouts-from-the-api/93737)

## Uploading planned workouts

See [Uploading planned workouts to Intervals.icu](https://forum.intervals.icu/t/uploading-planned-workouts-to-intervals-icu/63624)

## Updating wellness data

Push weight, resting HR, HRV, steps etc. using the [bulk wellness upload endpoint](https://intervals.icu/api-docs.html#put-/api/v1/athlete/-id-/wellness-bulk) (WELLNESS:WRITE scope required):

```bash
curl -X PUT 'https://intervals.icu/api/v1/athlete/0/wellness-bulk' \
    -H 'Authorization: Bearer d842c1fc25f241e5ae440d09756448a9' \
    -H 'Content-Type: application/json' \
    -d '[{"id":"2024-11-20","weight": 69.1},{"id":"2024-11-19","weight": 69.3}]'

```

Each object in the array must have an id (the ISO-8601 local date of the record) and whatever other fields you want to update. See the full docs for a list of possible fields. All units are metric.

## Downloading wellness data

Call the [list wellness endpoint](https://intervals.icu/api-docs.html#get-/api/v1/athlete/-id-/wellness-ext-) (WELLNESS:READ scope required):

```bash
curl 'https://intervals.icu/api/v1/athlete/0/wellness?oldest=2024-11-18&newest=2024-11-20' \
    -H 'Authorization: Bearer d842c1fc25f241e5ae440d09756448a9'

```

## Downloading completed activities

Call the [list activities endpoint](https://intervals.icu/api-docs.html#get-/api/v1/athlete/-id-/activities) (ACTIVITY:READ scope is required):

```bash
curl 'https://intervals.icu/api/v1/athlete/0/activities?oldest=2024-11-19&newest=2024-11-20' \
    -H 'Authorization: Bearer d842c1fc25f241e5ae440d09756448a9'

```

This returns summary data for each activity. Example abridged response:

```auto
[{
  "id": "i55751783",
  "start_date_local": "2024-11-20T07:35:18",
  "type": "Ride",
  ...
  "file_type": "fit",
  ...

```

To download the [original activity file](https://intervals.icu/api-docs.html#get-/api/v1/activity/-id-/file) (fit, gpx or tcx) gzip compressed:

```bash
curl 'https://intervals.icu/api/v1/activity/i55751783/file' \
    -H 'Authorization: Bearer d842c1fc25f241e5ae440d09756448a9' \
    > activity.fit.gz

```

To download the [Intervals.icu generated fit file](https://intervals.icu/api-docs.html#get-/api/v1/activity/-id-/fit-file) (with edits to activity power etc. and laps matching the intervals):

```bash
curl 'https://intervals.icu/api/v1/activity/i55751783/fit-file' \
    -H 'Authorization: Bearer d842c1fc25f241e5ae440d09756448a9' \
    > activity.fit.gz

```

Note that this is always a fit file even if the original file was a gpx or tcx.

## User Signup

The Intervals.icu signup form accepts query parameters to pre-populate the form with data you might already have:

> [https://intervals.icu/signup?email=foo&name=bar&ftp=280&max\_hr=188&sex=M&weight=69.6](https://intervals.icu/signup?email=foo&name=bar&ftp=280&max_hr=188&sex=M&weight=69.6)

We are considering a paid white label option to allow new users to be created using the API.

## Webhooks

Configure webhooks using the management page for your app. Look for your app in /settings and click “Manage App”.

Note that activity webhooks are not delivered for Strava activities.

The CALENDAR\_EVENT\_UPDATED and CALENDAR\_EVENT\_DELETED webhooks are legacy. Please use the CALENDAR\_UPDATED webhook instead.

The webhook POST payload includes a secret that you can use to verify that the webhook came from Intervals.icu.

```json
{
  "secret": "ooKeodacie8I",
  "events": [
    {
      "athlete_id": "...",
      "type": "ACTIVITY_UPLOADED",
      "timestamp": "2024-12-06T06:40:47.011+00:00",
      "activity": {...}
    }
  ]
}

```

Some webhooks (e.g. ACTIVITY\_ANALYZED) are sent after a 60s delay so multiple events for the same activity can be consolidated into one webhook.

You need to store the Intervals.icu athlete\_id obtained via OAuth flow so you can map the webhook back to the athlete in your system.

CALENDAR\_UPDATED webhooks include oauth\_client\_id (that created the event if any) and external\_id (ID supplied when the event was created, if any). You can use this to only look at events created by your app (oauth\_client\_id matches your client ID) and quickly update them using the external\_id.

```auto
{
  "secret": "...",
  "events": [
    {
      "athlete_id": "2049151",
      "type": "CALENDAR_UPDATED",
      "timestamp": "2024-12-18T10:22:16.427+00:00",
      "events": [
        {
          "id": 48566307,
          "start_date_local": "2024-12-20T00:00:00",
          "icu_training_load": 118,
          "type": "Ride",
          ...
        },
        {
          "id": 48566308,
          "start_date_local": "2024-12-21T00:00:00",
          "icu_training_load": 82,
          ...
        }
      ],
      "deleted_events": []
    }
  ]
}

```

The SPORT\_SETTINGS\_UPDATED webhook is fired when an athletes FTP, zones etc. are changed.

---

<div class="post-metadata">

**Author:** ![BruceLaw](https://forum.intervals.icu/user_avatar/forum.intervals.icu/brucelaw/32/29074_2.png) [@BruceLaw](https://forum.intervals.icu/u/BruceLaw)\
**Post date:** [3 February 2025 19:25 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090/3 "2025-02-03T19:25:17Z")

</div>

Hi David.

Can textevents be made in intervals.icu workouts? I see that repeated intervals send textevents 1/5, 2/5 etc. Can instructions be saved as a textevent? Or do I need to export zwo, make the edit and upload back to intervals.icu?

Thanks

---

<div class="post-metadata">

**Author:** ![david](https://forum.intervals.icu/user_avatar/forum.intervals.icu/david/32/30189_2.png) [@david](https://forum.intervals.icu/u/david)\
**Post date:** [4 February 2025 03:57 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090/4 "2025-02-04T03:57:46Z")

</div>

You can create textevent’s in the workout builder:

 ![Screenshot 2025-02-04 at 05.54.39](https://forum.intervals.icu/uploads/default/original/3X/2/4/240ae0c84fc6293d8707fda428fcba4e2e335b10.png)

```
- Toddle along 30m Z2

```

ZWO:

```auto
<SteadyState show_avg="1" PowerHigh="0.75" PowerLow="0.56" Duration="1800">
    <textevent message="Toddle along" duration="10"/>
</SteadyState>

```

---

<div class="post-metadata">

**Author:** ![stezz](https://forum.intervals.icu/user_avatar/forum.intervals.icu/stezz/32/47977_2.png) [@stezz](https://forum.intervals.icu/u/stezz)\
**Post date:** [14 February 2025 16:01 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090/5 "2025-02-14T16:01:30Z")

</div>

@david I am trying to fix old data for `avg_sleep_hr` in the wellness data pre 07/02/25 (see [why here](https://forum.intervals.icu/t/oura-avg-sleep-hr-vs-lowest-resting-hr/85963/15)). I have tried to use both `wellness-bulk` and `wellness` endpoints but what is happening is that I can see the data being modified in the UI for a moment, and a moment later it goes back to what it was before, like the system was reverting any change made via the API to what it gets from Oura anyway.

Is this expected? I read [here](https://forum.intervals.icu/t/oura-ring-integration/4288/30) that

> Intervals.icu goes back about a week for Oura

But it seems, it only works if you modify that via the UI?

Can you please help me to understand?

Ps. if you wanted to fix you sync algorithm so that it does that by itself I am all happy 😃

---

<div class="post-metadata">

**Author:** ![MedTechCD](https://forum.intervals.icu/user_avatar/forum.intervals.icu/medtechcd/32/40383_2.png) [@MedTechCD](https://forum.intervals.icu/u/MedTechCD)\
**Post date:** [14 February 2025 17:22 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090/6 "2025-02-14T17:22:12Z")

</div>

I think you need to temporarily disable Oura sync , modify, then flag the wellness data as ´blocked´ and enable Oura sync again.  
Problem is that I don´t know if you can set ´blocked´ flag in bulk…

 ![image](https://forum.intervals.icu/uploads/default/original/3X/0/1/01be29b16e5f3d73fff24293b0a2d2bb347a7b8c.png)

---

<div class="post-metadata">

**Author:** ![app4g](https://forum.intervals.icu/user_avatar/forum.intervals.icu/app4g/32/41800_2.png) [@app4g](https://forum.intervals.icu/u/app4g)\
**Post date:** [14 February 2025 22:06 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090/7 "2025-02-14T22:06:22Z")

</div>

you can update a date for testing (note I added the lock = true)

> curl -X ‘PUT’ ‘[https://intervals.icu/api/v1/athlete/XXXX/wellness/2025-02-14](https://intervals.icu/api/v1/athlete/XXXX/wellness/2025-02-14)’ -H ‘accept: _/_’ -H ‘Authorization: Basic XXXX’ -H ‘Content-Type: application/json’ -d ‘{“vo2max”: 50,“steps”: 123,“locked”: true}’
> 
> {“steps”:123,“respiration”:null,“locked”:true,“Steps”:444}

After a few min or such, you can do a

> curl -X ‘GET’ ‘[https://intervals.icu/api/v1/athlete/xxxx/wellness/2025-02-13](https://intervals.icu/api/v1/athlete/xxxx/wellness/2025-02-13)’ -H ‘accept: _/_’ -H ‘Authorization: Basic xxxx’

and check if anything has changed. Unfotunately it won’t tell you “what” cause the change tho..

---

<div class="post-metadata">

**Author:** ![stezz](https://forum.intervals.icu/user_avatar/forum.intervals.icu/stezz/32/47977_2.png) [@stezz](https://forum.intervals.icu/u/stezz)\
**Post date:** [15 February 2025 07:53 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090/8 "2025-02-15T07:53:50Z")

</div>

That `'locked': True` seems to have done the trick 😉 thanks a lot!

---

<div class="post-metadata">

**Author:** ![stezz](https://forum.intervals.icu/user_avatar/forum.intervals.icu/stezz/32/47977_2.png) [@stezz](https://forum.intervals.icu/u/stezz)\
**Post date:** [15 February 2025 17:49 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090/9 "2025-02-15T17:49:04Z")

</div>

@david any chance that you could enable unlocking (i.e. setting `"locked": false`) via API for locked entries?

So one could unlock via API, change the wellness data, and then lock again?

(I had some small bugs in my script and now all the wellness data is locked 😃 )

---

<div class="post-metadata">

**Author:** ![Edoardo\_Gallo](https://forum.intervals.icu/letter_avatar_proxy/v4/letter/e/b487fb/32.png) [@Edoardo\_Gallo](https://forum.intervals.icu/u/Edoardo_Gallo)\
**Post date:** [10 March 2025 18:03 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090/10 "2025-03-10T18:03:57Z")

</div>

Hey all! Testing the integration using webhooks i noticed that the same event is delivered multiple times. Looks like it’s retrying even if my app responded with “204 No Content”.  
Anyone experienced the same?

@david this is the uid of the event i received multiple times: “f269e1c2-1fb3-4b85-993d-292943144360” and it’s a “CALENDAR\_UPDATED” type.  
Thanks!

---

<div class="post-metadata">

**Author:** ![david](https://forum.intervals.icu/user_avatar/forum.intervals.icu/david/32/30189_2.png) [@david](https://forum.intervals.icu/u/david)\
**Post date:** [10 March 2025 18:52 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090/11 "2025-03-10T18:52:38Z")

</div>

Please msg me the ID of your app and I will have a look. You might get multiple CALENDAR\_UPDATED webhooks for the same calendar events if they are being repeatedly changed.

---

<div class="post-metadata">

**Author:** ![david](https://forum.intervals.icu/user_avatar/forum.intervals.icu/david/32/30189_2.png) [@david](https://forum.intervals.icu/u/david)\
**Post date:** [10 March 2025 20:02 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090/12 "2025-03-10T20:02:26Z")

</div>

Ok Intervals.icu doesn’t like your 204 response and retries the webhook. I will fix that. Tx.

---

<div class="post-metadata">

**Author:** ![Edoardo\_Gallo](https://forum.intervals.icu/letter_avatar_proxy/v4/letter/e/b487fb/32.png) [@Edoardo\_Gallo](https://forum.intervals.icu/u/Edoardo_Gallo)\
**Post date:** [11 March 2025 08:13 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090/13 "2025-03-11T08:13:08Z")

</div>

Amazing, thank you!

---

<div class="post-metadata">

**Author:** ![Jefe\_Miguel](https://forum.intervals.icu/user_avatar/forum.intervals.icu/jefe_miguel/32/53773_2.png) [@Jefe\_Miguel](https://forum.intervals.icu/u/Jefe_Miguel)\
**Post date:** [19 March 2025 09:18 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090/14 "2025-03-19T09:18:15Z")

</div>

Hi, Is there a way of getting the list of athletes I am coaching using API? I haven’t been able to find it on the docs

EDIT: Just for the records.  
I found it. You can get athlete list using athlete-summary

---

<div class="post-metadata">

**Author:** ![Jefe\_Miguel](https://forum.intervals.icu/user_avatar/forum.intervals.icu/jefe_miguel/32/53773_2.png) [@Jefe\_Miguel](https://forum.intervals.icu/u/Jefe_Miguel)\
**Post date:** [28 March 2025 11:24 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090/15 "2025-03-28T11:24:49Z")

</div>

If I edit an activity do I need to PUT all the fields on the API call or just those that I am going to change?

---

<div class="post-metadata">

**Author:** ![david](https://forum.intervals.icu/user_avatar/forum.intervals.icu/david/32/30189_2.png) [@david](https://forum.intervals.icu/u/david)\
**Post date:** [28 March 2025 11:27 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090/16 "2025-03-28T11:27:55Z")

</div>

No, just the fields you want to update. Generally to clear a numeric field supply -1 as the value (most do not allow negative values).

---

<div class="post-metadata">

**Author:** ![david](https://forum.intervals.icu/user_avatar/forum.intervals.icu/david/32/30189_2.png) [@david](https://forum.intervals.icu/u/david)\
**Post date:** [1 April 2025 15:44 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090/17 "2025-04-01T15:44:19Z")

</div>

> [@BruceLaw](#):
>
> Can textevents be made in intervals.icu workouts? I see that repeated intervals send textevents 1/5, 2/5 etc. Can instructions be saved as a textevent?

Yes this got done:

> [@Text events are now supported in the workout builder](https://forum.intervals.icu/t/text-events-are-now-supported-in-the-workout-builder/96016):
>
> The workout builder now supports text events. Currently these only work in zwo (Zwift) files and workouts automatically uploaded using the Zwift integration. Other platforms and file formats get all the messages concatenated together. Here is an example: - First 60^30 Second 120^30 Third 10m 65% That creates a zwo like this: \<SteadyState show\_avg="1" Power="0.65" Duration="600"\> \<textevent message="First"/\> \<textevent message="Second" duration="30" timeoffset="60"/\> \<textevent mes…

---

<div class="post-metadata">

**Author:** ![Edoardo\_Gallo](https://forum.intervals.icu/letter_avatar_proxy/v4/letter/e/b487fb/32.png) [@Edoardo\_Gallo](https://forum.intervals.icu/u/Edoardo_Gallo)\
**Post date:** [24 April 2025 11:26 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090/18 "2025-04-24T11:26:26Z")

</div>

> [@david](#):
>
> You might get multiple CALENDAR\_UPDATED webhooks for the same calendar events if they are being repeatedly changed.

Hi David, do you think it’s possible to receive multiple CALENDAR\_UPDATED webhooks even for deleted events?

I’m troubleshooting a case where i got a CALENDAR\_UPDATED with a bunch of “deleted\_events” at “2025-04-14 17:53:53 +0000”.  
Around 3 minutes later (at 2025-04-14 17:56:10 +0000) i got another CALENDAR\_UPDATED with the same “event\_id” that got deleted on the previous webhook.  
The second webhook had all the events within the “events” list, and not the “deleted\_events” list so i re-created all the records on the app (but those are not visible on Intervals because they are gone).

Here a “event\_id: 61138214” to give you an example.

Do you know how/why the second webhook was triggered?  
Thanks!

---

<div class="post-metadata">

**Author:** ![Sensitron](https://forum.intervals.icu/user_avatar/forum.intervals.icu/sensitron/32/56043_2.png) [@Sensitron](https://forum.intervals.icu/u/Sensitron)\
**Post date:** [24 April 2025 15:53 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090/19 "2025-04-24T15:53:54Z")

</div>

I have question regarding the OpenApi Spec. I’m trying to generate a client but it fails for me. I tried different tools. Nswag and Openapi-generator-cli. When i validate the spec file i get the following error. What am i missing?

 ![image](https://forum.intervals.icu/uploads/default/original/3X/4/3/43f53f77575e5cbdbe2a7e812ff57eee286381b9.png)  
 ![image](https://forum.intervals.icu/uploads/default/original/3X/3/0/305b940f5b77522818d3e913d7765fc9af16e302.png)

---

<div class="post-metadata">

**Author:** ![david](https://forum.intervals.icu/user_avatar/forum.intervals.icu/david/32/30189_2.png) [@david](https://forum.intervals.icu/u/david)\
**Post date:** [25 April 2025 05:02 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090/20 "2025-04-25T05:02:15Z")

</div>

> [@Edoardo\_Gallo](#):
>
> do you think it’s possible to receive multiple CALENDAR\_UPDATED webhooks even for deleted events?

Yes it is. The updated one is queued up with the data and the event can be deleted before it gets sent. The webhook processing code doesn’t re-query the data. I might change this but there will always be a race condition with delete.

If your webhook handler doesn’t return a 2xx status code then the webhook will be re-fired for a while with exponential backoff.

---

<div class="post-metadata">

**Author:** ![david](https://forum.intervals.icu/user_avatar/forum.intervals.icu/david/32/30189_2.png) [@david](https://forum.intervals.icu/u/david)\
**Post date:** [25 April 2025 05:44 UTC](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090/21 "2025-04-25T05:44:48Z")

</div>

> [@Sensitron](#):
>
> When i validate the spec file i get the following error. What am i missing?

I have fixed these errors. Tx.

[Next page](https://forum.intervals.icu/t/intervals-icu-api-integration-cookbook/80090.md?page=2)
