I, or I should say my dog, received a Whistle Activity Monitor as a gift. I’ve been a Fitbit user for a while, so this seemed like an interesting experiment. (My dog is now wearing the Whistle and a Fitbit Zip on her collar so I can compare the two.)
Ketzel with her Whistle and Fitbit
So far, the Whistle is a pretty decent product, though expensive and limited. At the moment, the only way to access information about your dog is through the iPhone/iOS app. They don’t provide any interface to view the data on the web, no API, and no data export utility.
I spent some time this evening trying to figure out if I could pull the raw data directly from the Whistle server (to make it easier to compare to the Fitbit data, and maybe build a web page)
Using Charles Proxy I was able to view the HTTPS requests being made by the Whistle iOS app. You can mimic these calls (using something like Request Maker , cURL, or your own script) Here are raw details for logging in and requesting data:
Login
Request
https://app.whistle.com/api/tokens.json
POST /api/tokens.json HTTP/1.1
Host: app.whistle.com
Proxy-Connection: keep-alive
Accept: */*
Accept-Encoding: gzip, deflate
Content-Length: 179
Content-Type: application/json
Accept-Language: en;q=1, fr;q=0.9, de;q=0.8, ja;q=0.7, nl;q=0.6, it;q=0.5
Connection: keep-alive
User-Agent: WhistleApp/102 (iPhone; iOS 7.0.4; Scale/2.00)
{"password":"MY_WHISTLE_PASSWORD,"email":"MY_EMAIL_ADDRESS","app_id":"com.whistle.WhistleApp"}
Response
{"success":true,"token":"a_response_token_will_be_here"}
The token in the login response will then be passed in the “X-Whistle-AuthToken” HTTP header for followup requests. Note, this is logging in with a Whistle account. I think you can also login with a Facebook and some other 3rd party account. I didn’t try this.
Get information about your dog
Request
https://app.whistle.com/api/dogs
GET /api/dogs HTTP/1.1
Host: app.whistle.com
User-Agent: WhistleApp/102 (iPhone; iOS 7.0.4; Scale/2.00)
Proxy-Connection: keep-alive
Accept: */*
Accept-Encoding: gzip, deflate
Content-Type: application/json
Accept-Language: en;q=1, fr;q=0.9, de;q=0.8, ja;q=0.7, nl;q=0.6, it;q=0.5
Connection: keep-alive
X-Whistle-AuthToken: your_token_that_was_returned_in_the_login_response
Response (JSON)
[{
"id": "9999",
"city_id": "12345",
"date_of_birth": "2005-01-01",
"gender": "f",
"name": "Your Dog's Name",
"weight": 75.0,
"breed_name": "Dog Breed",
"photo_url": "https://dog-photos-production.s3.amazonaws.com/dog-9999-photo/abcdef.jpg",
"photo_url_sizes": {
"30x30": "https://dog-photos-production.s3.amazonaws.com/dog-9999-photo/_30x30_abcdef.jpg",
"60x60": "https://dog-photos-production.s3.amazonaws.com/dog-9999-photo/_60x60_abcdef.jpg",
"640x1136": "https://dog-photos-production.s3.amazonaws.com/dog-9999-photo/_640x1136_abcdef.jpg",
"640x960": "https://dog-photos-production.s3.amazonaws.com/dog-9999-photo/_640x960_abcdef.jpg",
"320x480": "https://dog-photos-production.s3.amazonaws.com/dog-9999-photo/_320x480_abcdef.jpg"
},
"location": "City, ST",
"profile_photo_url": "https://dog-photos-production.s3.amazonaws.com/dog-9999-photo/profile_ghijkl.jpg",
"profile_photo_url_sizes": {
"30x30": "https://dog-photos-production.s3.amazonaws.com/dog-9999-photo/_30x30_profile_ghijkl.jpg",
"60x60": "https://dog-photos-production.s3.amazonaws.com/dog-9999-photo/_60x60_profile_ghijkl.jpg",
"166x166": "https://dog-photos-production.s3.amazonaws.com/dog-9999-photo/_166x166_profile_ghijkl.jpg",
"332x332": "https://dog-photos-production.s3.amazonaws.com/dog-9999-photo/_332x332_profile_ghijkl.jpg"
},
"relationship": "owner",
"device_id": "3456",
"current_activity_goal": {
"minutes": 90,
"started_at": "2014-01-01T00:00:00-05:00 America/New_York"
}
}]
Get status data about the Whistle device
Request
https://app.whistle.com/api/devices/3456
GET /api/devices/3456 HTTP/1.1
Host: app.whistle.com
User-Agent: WhistleApp/102 (iPhone; iOS 7.0.4; Scale/2.00)
Proxy-Connection: keep-alive
Accept: */*
Accept-Encoding: gzip, deflate
Content-Type: application/json
Accept-Language: en;q=1, fr;q=0.9, de;q=0.8, ja;q=0.7, nl;q=0.6, it;q=0.5
Connection: keep-alive
X-Whistle-AuthToken: your_token_that_was_returned_in_the_login_response
Response (JSON)
{
"serial_number": "W06-010ABC",
"firmware_version": "1.0-131021 :: 1.1-131021 :: 1.10-131119-a",
"battery_level": 42.750503100560245,
"last_check_in": "2014-01-17T20:15:39-05:00 America/New_York",
"next_check_in": "2014-01-17T21:15:39-05:00 America/New_York",
"bluetooth_mac": null,
"wifi_mac": null,
"battery_days_left": 5.404172523606706
}
Get metric data
Request
https://app.whistle.com/api/dogs/9999/metrics?date=2014-01-03&id=9999
GET /api/dogs/9999/metrics?date=2014-01-03&id=9999 HTTP/1.1
Host: app.whistle.com
User-Agent: WhistleApp/102 (iPhone; iOS 7.0.4; Scale/2.00)
Proxy-Connection: keep-alive
Accept: */*
Accept-Encoding: gzip, deflate
Content-Type: application/json
Accept-Language: en;q=1, fr;q=0.9, de;q=0.8, ja;q=0.7, nl;q=0.6, it;q=0.5
Connection: keep-alive
X-Whistle-AuthToken: your_token_that_was_returned_in_the_login_response
Response (JSON)
{
"date": "2014-01-03T00:00:00-05:00",
"summary": {
"total_minutes_active": 84,
"total_minutes_rest": 1089
},
"events": [{
"id": "10426",
"start_time": "2014-01-03T07:04:30-05:00 America/New_York",
"end_time": "2014-01-03T07:43:30-05:00 America/New_York",
"event_type": "Walk",
"event_title": "39 minutes",
"event_details": null,
"minutes_active": 39,
"manually_created": false,
"users": ["you"],
"comments": [],
"comments_count": 0
}, {
"id": "10610",
"start_time": "2014-01-03T09:51:31-05:00 America/New_York",
"end_time": "2014-01-03T10:19:31-05:00 America/New_York",
"event_type": "Unknown",
"event_title": "14 minutes",
"event_details": null,
"minutes_active": 14,
"manually_created": false,
"users": ["SpouseName"],
"comments": [],
"comments_count": 0
}, {
"id": "11448",
"start_time": "2014-01-03T17:06:32-05:00 America/New_York",
"end_time": "2014-01-03T17:26:34-05:00 America/New_York",
"event_type": "Walk",
"event_title": "14 minutes",
"event_details": null,
"minutes_active": 14,
"manually_created": false,
"users": [],
"comments": [],
"comments_count": 0
}, {
"id": "12195",
"start_time": "2014-01-03T21:15:35-05:00 America/New_York",
"end_time": "2014-01-03T21:33:35-05:00 America/New_York",
"event_type": "Walk",
"event_title": "17 minutes",
"event_details": null,
"minutes_active": 17,
"manually_created": false,
"users": ["you", "SpouseName"],
"comments": [],
"comments_count": 0
}],
"point_entries": [{
"timestamp": "2014-01-03T00:00:00-05:00",
"measurement": 0
}, {
"timestamp": "2014-01-03T00:30:00-05:00",
"measurement": 0.9297725
}, {
"timestamp": "2014-01-03T01:00:00-05:00",
"measurement": 0.0
}, {
"timestamp": "2014-01-03T01:30:00-05:00",
"measurement": 26.538085939
}, {
"timestamp": "2014-01-03T02:00:00-05:00",
"measurement": 34.948730469
}, {
"timestamp": "2014-01-03T02:30:00-05:00",
"measurement": 0.0
}, {
"timestamp": "2014-01-03T03:00:00-05:00",
"measurement": 45.434570313
}, {
"timestamp": "2014-01-03T03:30:00-05:00",
"measurement": 0.390625
}, {
"timestamp": "2014-01-03T04:00:00-05:00",
"measurement": 63.964843751000004
}, {
"timestamp": "2014-01-03T04:30:00-05:00",
"measurement": 15.719449448999999
}, {
"timestamp": "2014-01-03T05:00:00-05:00",
"measurement": 7.025024703
}, {
"timestamp": "2014-01-03T05:30:00-05:00",
"measurement": 0.0
}, {
"timestamp": "2014-01-03T06:00:00-05:00",
"measurement": 26.245117188000002
}, {
"timestamp": "2014-01-03T06:30:00-05:00",
"measurement": 13.256835939
}, {
"timestamp": "2014-01-03T07:00:00-05:00",
"measurement": 19.042968751
}, {
"timestamp": "2014-01-03T07:30:00-05:00",
"measurement": 11152.111816408
}, {
"timestamp": "2014-01-03T08:00:00-05:00",
"measurement": 5815.869140628001
}, {
"timestamp": "2014-01-03T08:30:00-05:00",
"measurement": 151.17821951599998
}, {
"timestamp": "2014-01-03T09:00:00-05:00",
"measurement": 25.939941407
}, {
"timestamp": "2014-01-03T09:30:00-05:00",
"measurement": 0.0
}, {
"timestamp": "2014-01-03T10:00:00-05:00",
"measurement": 2376.452636721
}, {
"timestamp": "2014-01-03T10:30:00-05:00",
"measurement": 4526.770019533001
}, {
"timestamp": "2014-01-03T11:00:00-05:00",
"measurement": 75.08544921999999
}, {
"timestamp": "2014-01-03T11:30:00-05:00",
"measurement": 67.87217916700001
}, {
"timestamp": "2014-01-03T12:00:00-05:00",
"measurement": 0.366210938
}, {
"timestamp": "2014-01-03T12:30:00-05:00",
"measurement": 3.784179688
}, {
"timestamp": "2014-01-03T13:00:00-05:00",
"measurement": 43.823242188
}, {
"timestamp": "2014-01-03T13:30:00-05:00",
"measurement": 9.887695313
}, {
"timestamp": "2014-01-03T14:00:00-05:00",
"measurement": 0.0
}, {
"timestamp": "2014-01-03T14:30:00-05:00",
"measurement": 43.334960937999995
}, {
"timestamp": "2014-01-03T15:00:00-05:00",
"measurement": 6.884765626
}, {
"timestamp": "2014-01-03T15:30:00-05:00",
"measurement": 93.981933596
}, {
"timestamp": "2014-01-03T16:00:00-05:00",
"measurement": 37.866210937999995
}, {
"timestamp": "2014-01-03T16:30:00-05:00",
"measurement": 34.631347657
}, {
"timestamp": "2014-01-03T17:00:00-05:00",
"measurement": 11.547851563
}, {
"timestamp": "2014-01-03T17:30:00-05:00",
"measurement": 7009.921468534001
}, {
"timestamp": "2014-01-03T18:00:00-05:00",
"measurement": 0
}, {
"timestamp": "2014-01-03T18:30:00-05:00",
"measurement": 15.417480469000001
}, {
"timestamp": "2014-01-03T19:00:00-05:00",
"measurement": 252.099609377
}, {
"timestamp": "2014-01-03T19:30:00-05:00",
"measurement": 446.12486734699996
}, {
"timestamp": "2014-01-03T20:00:00-05:00",
"measurement": 1.721191406
}, {
"timestamp": "2014-01-03T20:30:00-05:00",
"measurement": 5.083799915
}, {
"timestamp": "2014-01-03T21:00:00-05:00",
"measurement": 8.203125001
}, {
"timestamp": "2014-01-03T21:30:00-05:00",
"measurement": 6402.282714841999
}, {
"timestamp": "2014-01-03T22:00:00-05:00",
"measurement": 1084.9121093790002
}, {
"timestamp": "2014-01-03T22:30:00-05:00",
"measurement": 0.0
}, {
"timestamp": "2014-01-03T23:00:00-05:00",
"measurement": 29.750045897
}, {
"timestamp": "2014-01-03T23:30:00-05:00",
"measurement": 2.560905829
}, {
"timestamp": "2014-01-04T00:00:00-05:00",
"measurement": 0.0
}],
"activity_goal": {
"minutes": 90,
"started_at": "2014-01-01T00:00:00-05:00 America/New_York"
}
Getting trend data
Request
https://app.whistle.com/api/dogs/9999/trends?date=2014-01-13
GET /api/dogs/9999/trends?date=2014-01-13 HTTP/1.1
Host: app.whistle.com
User-Agent: WhistleApp/102 (iPhone; iOS 7.0.4; Scale/2.00)
Proxy-Connection: keep-alive
Accept: */*
Accept-Encoding: gzip, deflate
Content-Type: application/json
Accept-Language: en;q=1, fr;q=0.9, de;q=0.8, ja;q=0.7, nl;q=0.6, it;q=0.5
Connection: keep-alive
X-Whistle-AuthToken: your_token_that_was_returned_in_the_login_response
Response (JSON)
{
"days": {
"2014-01-13T00:00:00-05:00": {
"total_minutes_active": 115,
"total_minutes_rest": 1131,
"activity_goal": 90,
"average_minutes_active": 124.666666666667,
"average_minutes_rest": 1090.66666666667
},
"2014-01-14T00:00:00-05:00": {
"total_minutes_active": 103,
"total_minutes_rest": 1141,
"activity_goal": 90,
"average_minutes_active": 107.333333333333,
"average_minutes_rest": 1084.33333333333
},
"2014-01-15T00:00:00-05:00": {
"total_minutes_active": 101,
"total_minutes_rest": 1155,
"activity_goal": 90,
"average_minutes_active": 112.333333333333,
"average_minutes_rest": 1066.66666666667
},
"2014-01-16T00:00:00-05:00": {
"total_minutes_active": 125,
"total_minutes_rest": 1075,
"activity_goal": 90,
"average_minutes_active": 121.333333333333,
"average_minutes_rest": 1102.66666666667
},
"2014-01-17T00:00:00-05:00": {
"total_minutes_active": null,
"total_minutes_rest": null,
"activity_goal": 90,
"average_minutes_active": 105.5,
"average_minutes_rest": 1060.0
},
"2014-01-18T00:00:00-05:00": {
"total_minutes_active": null,
"total_minutes_rest": null,
"activity_goal": 90,
"average_minutes_active": 73.3333333333333,
"average_minutes_rest": 802.0
},
"2014-01-19T00:00:00-05:00": {
"total_minutes_active": null,
"total_minutes_rest": null,
"activity_goal": 90,
"average_minutes_active": 103.333333333333,
"average_minutes_rest": 1095.66666666667
}
},
"number_days_goal_reached": 4,
"overall_activity_percent_change": 3.84,
"overall_rest_percent_change": 7.98,
"similar_dogs_minutes_rest": 1014.8785460992899,
"similar_dogs_minutes_active": 43.8461879432624,
"similar_dogs_rest_percent_difference": 10.9,
"similar_dogs_activity_percent_difference": 153.16,
"historical_average_minutes_rest": 1042.3,
"historical_average_minutes_active": 106.9,
"current_average_minutes_rest": 1125.5,
"current_average_minutes_active": 111.0,
"average_minutes_rest": 1042.3,
"average_minutes_active": 106.9,
"total_minutes_rest": 1125.5,
"total_minutes_active": 111.0,
"next_week_valid": false,
"previous_week_valid": true
}
It looks like there are some more minor API calls, like setting the activity goal. If I end up persuing this further, I’ll add those later.