Skip to content
danielpapenfuss edited this page May 9, 2015 · 6 revisions

Runtastic Pro App Analysis

To get rid of the dirty DOM-Parsing in the current version of "php-runtastic" i started tinkering around the API calls send from the Runtastic Pro iOS App.

Here are some details for anyone interested.

Login

To receive data from the apps' backend, you have to login first. It can be done by sending a uncompressed JSON string via POST request.

URI: https://appws.runtastic.com/webapps/services/auth/login

Type: POST

Content-Type: JSON

POST-Data

{ "additionalAttributes": [ "accessToken" ], "email": "[email protected]", "password": "thepassword" }

Header

The mandatory Request-Header Attributes are:

(Note: Content-Length and X-Date have to be generated at runtime)

(Note 2: X-Auth-Token depends on X-Date - so it also have to be generated)

Host: appws.runtastic.com
X-App-Key: at.runtastic.runtastic.pro
Content-Length: 56
X-Auth-Token: 5b3e1c74b148d0229f4b2fb299eb952966f93c24
X-Date: 2014.12.12 20:25:20
Connection: keep-alive
X-App-Feature-Set: pro
Accept: /
Content-Type: application/json
Accept-Encoding: deflate

Response

The response is a uncompressed JSON string:

{
"userId": "13374711",
"accessToken": "238af7a6fsdg998sdg7s6asfas77f9sd4as4da433as56f7sdg77s7f65a7s7dsa",
"uidt": "89a9sdaaa9a6f67dg7s9sg987asd8hj9rq8a6s6c"
}

User-ID and Access-Token are needed to proceed with further calls to the backend.

User data

After getting User-ID and Access-Token, its possible to receive all the user info for the logged-in user via a simple GET request.

URI: https://appws.runtastic.com/webapps/services/users/13374711?access_token= 238af7a6fsdg998sdg7s6asfas77f9sd4as4da433as56f7sdg77s7f65a7s7dsa

Type: GET

Header

The header attributes from the login are still valid while doing the GET request. As far as i noticed, changes in the header don't affect the connection as long as its the same session and the cookies are still stored.

Response

The response is a gzip-compressed JSON string.

{
"firstName": "Donald",
"lastName": "Duck",
"height": "1.86",
"weight": "84.0",
"gender": "M",
"birthday": "567043200000",
"countryCode": "DE",
"locale": "en",
"unit": "0",
"avatarUrl": "http://avatars0.runtastic.com/files/5234284/big.avatar_upload_ 13374711.JPG?updatedAt=1402413757000",
"weightUnit": "0",
"temperatureUnit": "0",
"liquidUnit": "0",
"agbAccepted": "true",
"localytics": "Gold-Paid.>12M",
"membershipStatus": "gold.paid",
"createdAt": "1305832375000",
"subscriptions": [
{
"id": "15698267",
"updatedAt": "1417224709000",
"status": "paid",
"planName": "gold",
"validFrom": "1417377307000",
"validTo": "1448913307000",
"paidContractSince": "1385841307000",
"active": "true",
"paymentProvider": "adyen",
"paymentProviderText": "Runtastic.com"
}
],
"email": "[email protected]",
"isEmailConfirmed": "true"
},
"userSettings": {
"myFitnessPalConnected": "true"
},
"dataChanged": "true"
}

Session data

To receive session data, you have to perform a POST request containing the number of sessions per page (Attribute: "perPage") and the last sync date (Attribute: "syncedUntil). Since we are using the app backend, we can use the sync mechanisms in place to load all the sessions onto the device for offline use. That means if we want to receive all the sessions available, we set the number of sessions per page to a high number (e.g 2000) and the "syncedUntil" attribute to a timestamp years ago (e.g 946684800 (Unix time for 01.01.2010 00:00:00). To perform the request, the access-token received at login time has to be attached to the URL.

URI: https://appws.runtastic.com/webapps/services/runsessions/v3/sync?access_token= 238af7a6fsdg998sdg7s6asfas77f9sd4as4da433as56f7sdg77s7f65a7s7dsa

Type: POST

Content-Type: JSON

POST-Data

{ "perPage": "2000", "syncedUntil": "946684800" }

Header

The mandatory Request-Header Attributes are:

(Note: Content-Length and X-Date have to be generated at runtime)

Host: appws.runtastic.com
X-App-Key: at.runtastic.runtastic.pro
Content-Length: 56
X-Auth-Token: 5b3e1c74b148d0229f4b2fb299eb952966f93c24
X-Date: 2014.12.12 20:25:20
Connection: keep-alive
X-App-Feature-Set: pro
Accept: /
Content-Type: application/json
Accept-Encoding: deflate

Response

The response is a gzip-compressed JSON string. In the example above, only two sessions are visible.

{
"syncedUntil": "1418418819229",
"perPage": "2000",
"sessions": [
{
"id": "9273971",
"sportTypeId": "15",
"distance": "9500",
"duration": "900000",
"startTime": "1330490983000",
"endTime": "1330491883000",
"calories": "566",
"elevationGain": "0",
"elevationLoss": "0",
"manual": "true",
"sportDeviceId": "3807",
"edited": "false",
"updatedAt": "1330492278000",
"pause": "0",
"speedData": {
"avg": "38.0",
"max": "0.0"
},
"additionalInfoData": {
"weatherId": "2",
"feelingId": "1"
},
"indoor": "false",
"gpsTraceAvailable": "false",
"heartRateAvailable": "false",
"cadenceTraceAvailable": "false",
"stepTraceAvailable": "false",
"altitudeTraceAvailable": "false",
"speedTraceAvailable": "false"
},
{
"id": "148005739",
"sportTypeId": "19",
"distance": "12359",
"duration": "8249984",
"startTime": "1385832681000",
"endTime": "1385840931000",
"calories": "756",
"elevationGain": "28",
"elevationLoss": "28",
"manual": "false",
"sportDeviceId": "56752",
"edited": "false",
"updatedAt": "1385841082000",
"pause": "16",
"gpsData": {
"longitude": "10.5037365",
"latitude": "52.272583"
},
"speedData": {
"avg": "5.393",
"max": "12.024"
},
"additionalInfoData": {
"weatherId": "2",
"feelingId": "1",
"surfaceId": "2",
"temperature": "5.0"
},
"gradientData": {
"zones": [
{
"name": "zone1",
"distance": "200",
"duration": "196358",
"min": "-2.0",
"max": "-90.0"
},
{
"name": "zone2",
"distance": "11700",
"duration": "7756052",
"min": "-2.0",
"max": "2.0"
},
{
"name": "zone3",
"distance": "400",
"duration": "256943",
"min": "2.0",
"max": "90.0"
}
],
"avgUp": "2.906666",
"avgDown": "3.644768",
"maxUp": "3.337932",
"maxDown": "3.734207"
},
"liveTrackingEnabled": "false",
"indoor": "false",
"extData": {
"gradientData": {
"avgUp": "2.906666",
"avgDown": "3.644768",
"maxUp": "3.337932",
"maxDown": "3.734207"
}
},
"gpsTraceAvailable": "true",
"heartRateAvailable": "false",
"cadenceTraceAvailable": "false",
"stepTraceAvailable": "false",
"altitudeTraceAvailable": "false",
"speedTraceAvailable": "false",
"encodedTrace": "sn~Hiob_ABCkAvGl@xObIGdJBxKoAjKl@tKjApKqBnK}GpKt@Mz@tJmIhIgUpHwLrJrCKP`@eOl@oSoBkQcHkN}G{JF}S_AkSwFqKg@qOuAaQcAqP{DeMmGqImK_CsK_ByJqCyJwIwJp@kKtFcK|FoJDsKfGqJ~E}K|Dk@rMOhTFdQYbSjAhRpBjUhCvTbC|RvAtRpClPu@lD",
"records": {
"achievements": {
"fastestKm": "553900",
"fastestMi": "952068",
"fastest3Mi": "3080990",
"fastest5k": "3219988",
"fastest10k": "6475988"
},
"positions": {
"fastestKm": "1385840132000",
"fastestMi": "1385833942000",
"fastest3Mi": "1385832901000",
"fastest5k": "1385833369000",
"fastest10k": "1385833369000"
}
}
}
]
}