Whoop

Backlinks: Fitness Tech | Wearables > Whoop

Whoop! Super accurate fitness and recovery tracker.

Bicep band is a MUST.

See Whoop Heart Rate Accuracy.

Whoop band 4

I haven't noticed a big difference from the 3.0, though the health metrics have picked up sickness. Waterproof charger is a plus. I couldn't passed on getting new bands ($$).

Whoop band 3

My first band. Lives in a drawer in the basement.

Whoop labs

Did this once. Cool experience. Wrote about it on Reddit, I think.

Code to pull from official API

# coding: utf-8
import requests
import urllib.parse

# Set client ID and secret as variables
client_id = 'a9ca87d7-4801-4cfa-8a73-ca3ec64a553e'
client_secret = 'db0f5ff632ee3639e9d505ff2086b9cc98a26b4ece88160cda0be911873ca608'

# Construct authorization URL
auth_endpoint = 'https://api.prod.whoop.com/oauth/oauth2/auth'
response_type = 'code'
state = 'XXXXXXXX'
scope = 'offline read:recovery read:sleep read:workout'
redirect_uri = 'http://localhost'

query_params = {
    'response_type': response_type,
    'state': state,
    'client_id': client_id,
    'scope': scope,
    'redirect_uri': redirect_uri,
}

url_parts = list(urllib.parse.urlparse(auth_endpoint))
query = dict(urllib.parse.parse_qsl(url_parts[4]))
query.update(query_params)

url_parts[4] = urllib.parse.urlencode(query)
auth_url = urllib.parse.urlunparse(url_parts)

# Print authorization URL
print(auth_url)

# Go get the code
# Put it here:
code = 'NKt6SgG3GWSTeLzuJMUgEvNpsSWwN_StQ7kmB6ru_Tc.heo0badPmQFjqVlBPzIm_8RCtkm3TzL5CCXLtfAshc8'

token_endpoint = 'https://api.prod.whoop.com/oauth/oauth2/token'

headers = {
    'Content-Type': 'application/x-www-form-urlencoded',
}

data = {
    'grant_type': 'authorization_code',
    'code': code,
    'redirect_uri': redirect_uri,
    'client_id': client_id,
    'client_secret': client_secret,
}

response = requests.post(token_endpoint, headers=headers, data=data)
access_token = response.json()['access_token']

# Print access token
print(f'Access token: {access_token}')
# FsgywB18lYaXjbVDEsPyUqoQT3YrLQZCXErOJ7tI6GA.1OvyFRr1BtMxUwii4v_YI64x7A0x1QaD_1bSMCyPxp0',
# Print full reponse
print(response.json())
# {'access_token': 'FsgywB18lYaXjbVDEsPyUqoQT3YrLQZCXErOJ7tI6GA.1OvyFRr1BtMxUwii4v_YI64x7A0x1QaD_1bSMCyPxp0',
#  'expires_in': 3600,
#  'refresh_token': '_E6oQ0Dlt1XxKmkC9vIXd0qIJUWQ4shS6kIu9dydKsI.4utCHFS66910VSNUfnrIvF4M86i0nehB2Sx6dZk7iPM',
#  'scope': 'offline read:recovery read:sleep read:workout',
#  'token_type': 'bearer'}