Xert Online API Version 1.7
REST and OAuth methods for authenticating with Xert Online, reading fitness, workout and activity data, and uploading activities.
- 1.7
- Added a Developer tab to Account Settings where you can view your personal client key and generate a client secret.
- Added the
client_credentialsgrant: exchange your client key and secret for an access token for your own account. These tokens can only call the endpoints documented here (training info, workouts, activities and upload), and they include activities recorded on a Garmin device. - The shared
xert_publicclient now returns fixed demo data (one demo activity and workout) instead of the logged-in user’s own data. - 1.5
- Added OAuth Authorization Code flow for third-party applications.
- 1.3
- Added new Training and Fitness Info endpoint.
- 1.2
- Added Progression and Training Status to Activity Details.
- 1.1
- Added Session, Images, Previous Signature and Medal return values to Activity Details.
This document specifies the OAuth and REST methods to use to perform the following functions:
- Obtain and refresh a user authorization token
- Authorize a third-party application via the OAuth Authorization Code flow
- Get a list of workouts for an authenticated user
- Retrieve a workout definition
- Upload a FIT file
The curl commands are examples of how calls should be made. Any questions should be sent to support@xertonline.com.
Requesting a client key and secret
Every Xert account can create its own API credentials from the Developer tab of Account Settings. You need them to call the API on your own behalf.
- Log in to xertonline.com with the account whose data you want to access. (If you are not logged in, the link above will take you to the login page first.)
- Open the Developer tab of Account Settings (or choose Account Settings from the user menu at the top right, then the Developer tab).
- Your Client Key is already shown (it begins with
xk_). Use the Copy button to copy it. The key identifies your account and never changes. - Click Generate Secret. Your Client Secret (it begins with
xs_) appears below the button. Use the Copy button and store it somewhere safe, such as a password manager or your application’s secrets store. - Use the key and secret to request an access token.
- You can have one key and secret per Xert account.
- Regenerating the secret, or clicking Delete Secret, immediately stops the old secret from working and invalidates every access token that was issued with it. Update anything that uses it.
- Keep the secret private. Anyone who has it can read your Xert data and upload activities to your account. If you think it has been exposed, regenerate or delete it straight away.
- The Developer tab is only available to the account owner, not to a coach or administrator who is viewing the site as that user.
Token authentication
Obtaining a token
xert_public client returns demo data only. A token obtained with xert_public:xert_public always reads and returns a fixed demo activity and workout, not the logged-in user’s own data. To work with your own data, use a client key and secret (or, for a third-party application, the Authorization Code flow). The xert_public examples below show the shape of the requests./oauth/tokencurl -u xert_public:xert_public -X POST "https://www.xertonline.com/oauth/token" \
-d 'grant_type=password' \
-d 'username=AzureDiamond' \
-d 'password=Hunter2'{
"access_token": "ee88cf29984d4fe205ca574ec1edd01ae67d74d2",
"expires_in": 604800,
"token_type": "Bearer",
"scope": "basic",
"refresh_token": "1badfdee0f72b847dc91d1baf9e5c095c774c14a"
}Refreshing a token
/oauth/tokencurl -u xert_public:xert_public -X POST "https://www.xertonline.com/oauth/token" \
-d 'grant_type=refresh_token' \
-d 'refresh_token=1badfdee0f72b847dc91d1baf9e5c095c774c14a'{
"access_token": "7d3fc2e8cb80caffd881f08e764e1b507168cce4",
"expires_in": 604800,
"token_type": "Bearer",
"scope": "basic",
"refresh_token": "da61ee326213aa9676c6e26faab41050b847bcc3"
}OAuth Authorization Code flow third-party apps
Third-party applications should use the Authorization Code flow: the user authenticates on xertonline.com itself and your application never sees their Xert credentials.
Step 1: Register your application
Client credentials are issued by Xert and are not available via self-service for third-party applications. Email support@xertonline.com with your application name and the redirect URI(s) you want to use, and we will issue you a client_id and client_secret.
Step 2: Redirect the user to Xert to authorize your application
Send the user’s browser to the authorize endpoint with the following query parameters:
/oauth/authorizeGET https://www.xertonline.com/oauth/authorize?client_id=your_client_id&redirect_uri=https://yourapp.example.com/callback&response_type=code&state=RANDOM_OPAQUE_STRING| Name | Type | Description |
|---|---|---|
client_id | String required | The client ID issued to your application. |
redirect_uri | String required | Where Xert should send the user back to after authorization; should match a URI you registered. |
response_type | String required | Always code. |
state | String recommended | An opaque value your application generates and can use to protect against CSRF; Xert returns it unmodified. We recommend always sending one, as Xert does not enforce it. |
If the user is not already logged in to Xert, they will be prompted to log in first. They are then shown a consent screen naming your application and the data it is requesting access to. On approval the user is redirected back to your redirect_uri with a one-time authorization code:
https://yourapp.example.com/callback?code=AUTHORIZATION_CODE&state=RANDOM_OPAQUE_STRINGStep 3: Exchange the code for an access token
/oauth/tokencurl -u your_client_id:your_client_secret -X POST "https://www.xertonline.com/oauth/token" \
-d 'grant_type=authorization_code' \
-d 'code=AUTHORIZATION_CODE' \
-d 'redirect_uri=https://yourapp.example.com/callback'The resulting access_token and refresh_token are used exactly as described under Token authentication: send the Authorization: Bearer header on subsequent API calls, and use the refresh_token grant to obtain a new access token once it expires.
Personal access with a client key and secret new in 1.7
If you only need to access your own Xert data (for example from a script or personal tool) you do not need the Authorization Code flow. You can create a client key and secret yourself from your Xert account – see Requesting a client key and secret above.
Obtaining a token with the client credentials grant
/oauth/tokencurl -u your_client_key:your_client_secret -X POST "https://www.xertonline.com/oauth/token" \
-d 'grant_type=client_credentials'{
"access_token": "1badfdee0f72b847dc91d1baf9e5c095c774c14a",
"expires_in": 604800,
"token_type": "Bearer",
"scope": "basic"
}The client key and secret may instead be sent as the client_id and client_secret form fields in the POST body. They are never accepted in the URL query string, where they would be recorded in server logs. Invalid credentials return HTTP 401 with {"error":"invalid_client"}.
Authorization: Bearer). A token issued this way:
- belongs to the account that owns the key, so it can only access that user’s data;
- can only call the endpoints documented below: training info, workout download, workouts, activities and upload. Any other endpoint returns HTTP 403 with
{"error":"insufficient_scope"}; - is valid for 7 days. Requesting a token again returns the same token until it has less than a day left, after which a new one is issued. No refresh token is issued: when it expires, request a new one with your client key and secret.
Getting user information
Retrieve a user’s training, fitness and status information
Obtain a user’s current fitness and training information.
/oauth/training_infocurl -X GET "https://www.xertonline.com/oauth/training_info?format=zwo" \
-H "Authorization: Bearer 7d3fc2e8cb80caffd881f08e764e1b507168cce4"If a format is given, the workout-of-the-day file is downloaded from the url in the response, using the same token:
curl -X GET "https://www.xertonline.com/oauth/workout-download/vovdxww5i7fzqbun.zwo" \
-H "Authorization: Bearer 7d3fc2e8cb80caffd881f08e764e1b507168cce4"Parameters
| Name | Type | Description |
|---|---|---|
format | String optional | Either erg or zwo: the format of the workout file to be returned. If not provided, no workout file will be returned. |
Response attributes
| Name | Type | Description |
|---|---|---|
success | Boolean | Indicates that the request is successful/unsuccessful. |
signature | Object | The user’s current fitness signature containing values for ftp, ltp, hie and pp representing Threshold Power, Lower Threshold Power, High Intensity Energy and Peak Power respectively. |
status | String | The user’s current training status: Detraining, Very fresh, Fresh, Tired, Very tired, representing the user’s current freshness status. |
weight | Number | The user’s weight in kilograms. |
tl | Object | The user’s current training loads containing values for low, high, peak, total representing the training load for each individual system and the total. |
targetXSS | Object | The user’s current training targets containing values for low, high, peak, total representing the training targets recommended by the system for each system and the total. |
source | String | The source of training information. |
wotd | Object | The user’s workout-of-the-day information (see below). |
wotd attributes
| Name | Type | Description |
|---|---|---|
type | String | Type of workout-of-the-day: None, Forecast or Scheduled, representing no workout-of-the-day, a workout-of-the-day assigned to a Forecast AI training placeholder, or a planned workout. |
name | String | Name of the workout. |
workoutId | String | Id of the workout. |
description | String | Description text. |
difficulty | Number | Difficulty score. |
url | String | URL of the workout file for the workout-of-the-day. Note that the URL will need to be requested with the user’s token. |
Sample response
{
"success": true,
"weight": 81,
"status": "Fresh",
"signature": {
"ltp": 243.9623680703351,
"ftp": 314.6014550178407,
"hie": 28.25563477900224,
"pp": 1251.5005813659977
},
"tl": {
"low": 123.32987160482055,
"high": 6.563550862763262,
"peak": 1.8767393717439016,
"total": 131.7701618393277
},
"source": "Workout",
"targetXSS": {
"low": 74.8839053229011,
"high": 0,
"peak": 0,
"total": 74.8839053229011
},
"wotd": {
"name": "AI Generated Workout",
"workoutId": "vovdxww5i7fzqbun",
"description": "AI generated workout powered by Xert\n\nThis has 3 declining sets starting with 2 x 07:53 at 242W with 00:00 rest in-between.",
"type": "Forecast",
"difficulty": 2.155537419064349,
"url": "https://www.xertonline.com/oauth/workout-download/vovdxww5i7fzqbun.zwo"
}
}Getting workouts
List workouts
A lightweight listing (path, name, description and last-modified time) of the authenticated user’s workouts. A token is required. Use /oauth/workouts below for the full workout definitions.
/oauth/workoutcurl -X GET "https://www.xertonline.com/oauth/workout" \
-H "Authorization: Bearer 7d3fc2e8cb80caffd881f08e764e1b507168cce4"{
"success": true,
"workouts": [
{
"path": "MGLq16v2O6vrILBi",
"name": "Default Workout",
"description": "",
"last_modified": 1457118394
}
]
}List a user’s workouts
/oauth/workoutscurl -X GET "https://www.xertonline.com/oauth/workouts" \
-H "Authorization: Bearer 7d3fc2e8cb80caffd881f08e764e1b507168cce4"{
"success": true,
"workouts": [
{
"path": "0GWegmcHSK6JKHth",
"name": "workout1",
"description": "Workout description",
"last_modified": 1456263137
},
{
"path": "ISm75NAmocJ7eUHr",
"name": "This is a workout",
"description": "Description goes here.",
"last_modified": 1457025192
}
]
}Retrieve a workout
Resolving a workout requires a specific user’s fitness signature to calculate power values in watts, as well as interval durations, so a user’s token is needed. Presently, the workout itself does not need to belong to the user whose token and signature are used.
The workout is specified by the path parameter given when obtaining a list of workouts. The parameter is added onto the request URL.
/oauth/workout/{path}curl -X GET "https://www.xertonline.com/oauth/workout/ISm75NAmocJ7eUHr" \
-H "Authorization: Bearer 7d3fc2e8cb80caffd881f08e764e1b507168cce4"{
"success": true,
"name": "test workout",
"description": "Description goes here",
"workout": [
{
"name": "Warmup",
"index": 0,
"power": 213,
"duration": 300,
"interval_count": 1
},
{
"name": "Intervals",
"index": 1,
"power": 557.5,
"duration": 48.106355492065,
"power_rest": 0,
"duration_rest": 20.821684998231,
"interval_count": 1
},
{
"name": "Intervals",
"index": 1,
"power": 557.5,
"duration": 13.206337103223,
"power_rest": 0,
"duration_rest": 20.821684998231,
"interval_count": 31
},
{
"name": "Cooldown",
"index": 2,
"power": 213,
"duration": 300,
"interval_count": 1
}
]
}The output’s workout field is an array where each entry represents a set of similar intervals. Each set specifies the name of the interval to which it belongs, its index in the workout designer, as well as the target power in watts and set duration in seconds for both the work and rest interval. As in the example above, one set in the workout designer may be split into multiple set entries in this API call, depending on how MPA and time-based intervals get resolved.
Getting activities
Retrieve a user’s activity
The activity is specified by the path parameter given when obtaining activity details. The parameter is added onto the request URL.
/oauth/activity/{path}curl -X GET "https://www.xertonline.com/oauth/activity/MGLq16v2O6vrILBi?include_session_data=1" \
-H "Authorization: Bearer 7d3fc2e8cb80caffd881f08e764e1b507168cce4"Parameters
| Name | Type | Description |
|---|---|---|
id / path | String required | The identifier of the activity. |
include_session_data | Integer optional | Values are 1 and 0; the default is 0. Set to 1 to include the session_data value in the response. |
Response attributes
| Name | Type | Description |
|---|---|---|
success | Boolean | Indicates that the request is successful. |
name | String | Name of the activity. |
description | String | Description of the activity. |
session_data | Object | Per-second MPA data object. |
summary | Object | Summary details of the activity including XSS, XLSS, XHSS, XPSS, XEP, MEP, TWS, SP, SFD, Focus, Specificity, Activity Type, Activity Date, Signature, Difficulty Rating, Distance (km), Duration (minutes), Difficulty, total grams of carbs, total grams of fat and Street View image. |
Sample response
{
"success": true,
"name": "Sample Title",
"description": "Sample Description",
"session_data": [
{
"power": 37.4,
"unix_time": 1501327986000,
"mpa": 2433.9170179125,
"cad": null,
"alt": 169,
"hr": null,
"spd": 39636,
"tgt": null,
"lat": 43.622447289526,
"lng": -79.792798189446,
"dist": 42605.14,
"tws": 147.53,
"xds": 84.483105789006
},
{
"power": 33.8,
"unix_time": 1501327987000,
"mpa": 2434.0872751323,
"cad": null,
"alt": 169,
"hr": null,
"spd": 39132,
"tgt": null,
"lat": 43.62251887098,
"lng": -79.792894916609,
"dist": 42616.01,
"tws": 147.53,
"xds": 84.444223240528
}
],
"summary": {
"session": {
"max_power": 765,
"avg_power": 234.37876486,
"max_cadence": 99,
"total_elevation_gain": 974,
"total_calories": 3903
},
"xss": 552.19146965358,
"xlss": 465.43228697047,
"xhss": 78.156195599284,
"xpss": 8.602987083827,
"xep": 200.59242389133,
"focus": "Rouleur",
"mep": 173,
"tws": 507736.33294669,
"sp": 0.51640347500268,
"sfd": 398.82093884511,
"specificity": "Mixed",
"difficulty": 256.78248819058,
"difficulty_rating": "5 - Hard",
"distance": 93.0061,
"duration": 13209,
"sig": {
"ftp": 250,
"atc": 41139,
"pp": 1447
},
"medal": 2,
"breakthrough": 1,
"prev_sig": {
"ftp": 300.48137188449,
"atc": 27284.630970804,
"pp": 1361.5247761747,
"ltp": 232.26979445748
},
"activity_type": "Cycling",
"start_date": {
"date": "2018-09-22 09:54:21.000000",
"timezone_type": 3,
"timezone": "UTC"
},
"total_grams_carbs": 248.78449606004,
"total_grams_fat": 120.94966162224,
"progression": {
"date": "2018-09-22 09:54:20",
"tl": {
"ftp": 92.706161948713,
"hie": 5.4803348960987,
"pp": 1.5145020294862
},
"rl": {
"ftp": 57.114808191115,
"hie": 4.5317592024292,
"pp": 0.93325134033198
},
"form": 37.121180140421
},
"training_status": 3.7057428249799,
"freshness": "Fresh",
"street_view": "https://xertonline.com/assets/images/maps/street_view/user-2/aortnwffwgptcxfy.png",
"activity_map": "https://xertonline.com/assets/images/maps/activity_map_aortnwffwgptcxfy.png",
"chart_view": "https://xertonline.com/assets/images/charts/aortnwffwgptcxfy"
}
}Retrieve a user’s activity list
The activity list is specified by a date range, from and to, which are added onto the request URL.
/oauth/activitycurl -X GET "https://www.xertonline.com/oauth/activity?from=1483230813&to=1506731613" \
-H "Authorization: Bearer 7d3fc2e8cb80caffd881f08e764e1b507168cce4"Parameters
| Name | Type | Description |
|---|---|---|
from | Integer (timestamp) required | A timestamp where the filtering of activities will start. |
to | Integer (timestamp) required | A timestamp where the filtering of activities will end. |
updated_from | Integer (timestamp) optional | A timestamp where the filtering of activities will start, based on updated time. |
Response attributes
| Name | Type | Description |
|---|---|---|
success | Boolean | Indicates that the request is successful. |
activities | Object | Activity details including name, description, activity type, start date and activity path/ID. |
Sample response
{
"success": true,
"activities": [
{
"name": "Morning Run",
"start_date": {
"date": "2017-08-12 11:08:29.000000",
"timezone_type": 3,
"timezone": "UTC"
},
"description": "Lorem Imposum",
"path": "4j9o4hhg5zrl0pah",
"activity_type": "Run"
},
{
"name": "Evening Ride",
"start_date": {
"date": "2017-08-12 11:08:29.000000",
"timezone_type": 3,
"timezone": "UTC"
},
"description": "Lorem Imposum",
"path": "4j9o4hhg5zrl0pah",
"activity_type": "Cycling"
}
]
}Uploading a .FIT file
The .FIT file needs to be posted to Xert Online as multipart/form-data.
/oauth/upload| Name | Type | Description |
|---|---|---|
file | File required | The .FIT file to upload. |
name | String optional | Sets the name of the activity. If omitted, the name of the .FIT file will be used. |
curl -X POST "https://www.xertonline.com/oauth/upload" \
-H "Authorization: Bearer 7d3fc2e8cb80caffd881f08e764e1b507168cce4" \
-F 'file=@workout.fit' \
-F 'name=Morning Ride'{
"success": true,
"json": {
"files": [
{
"name": "workout.fit",
"size": 55358,
"type": "application/octet-stream",
"url": "/activities/JOWCIbZWuvuLQoE7",
"deleteType": "DELETE",
"deleteUrl": "/activities/JOWCIbZWuvuLQoE7"
}
]
}
}The json field of the response is mostly meaningless and will be removed later.