/ #Go #Fitbit 

Using the Fitbit API from a command line application in Go

Learn how to access the Fitbit API using a command line application written in Go.

Fitbit is a company that produces activity trackers that measure things like steps, heart rate, and etc. They provide an API to interact with user data and for the past few days I have been playing with it. I basically wanted to download some activity data to plot charts and whatnots.

I followed the required steps, which includes registering a new app in their developer portal.

The first issue I found was with the authorization flow, which has the following steps:

  1. Your application redirects the user to Fitbit’s authorization page.
  2. Upon user consent, Fitbit redirects the user back to your application’s redirect URL with an authorization code as a URL parameter.
  3. Your application exchanges the authorization code for an access token and refresh token.
  4. Your application stores the access token and refresh token. It will use the access token to make requests to the Fitbit API. It will use the refresh token to obtain a new access token when the access token expires without having to re-prompt the user.

The problem with this flow is that it assumes that your application is web based. Well, I wasn’t going to spin up a web server every time I needed to authenticate to the API just to be able to run my command line application. Time to get creative.

The forever token

The last step in the authorization flow has the key to solve this problem. Once the authorization token is exchanged for an access token and a refresh token.

The access token is the token we will use to make requests to the Fitbit Web API. This token has an expiration date, though, and once it’s expired, we need to obtain a new one; for that we will use the refresh token.

The refresh token is used to obtain a new access token. A refresh token, unlike the access token, doesn’t expire until it is used. Once used, it will be invalidated and can’t be used again, but that’s alright because when we use it to obtain a new access token, a new refresh token will also be provided.

With these pair of tokens, you now have access to the API forever. Let’s see how we get these tokens.

Registering your app

  1. Navigate to Register an App.
  2. Fill all the required fields with any values you want, except for these two:
  3. OAuth 2.0 Application Type: Client
  4. Callback URL: http://localhost

Once you hit the register button, your app will be created and you should be ready to use it.

Obtaining an access code

  1. Navigate to Manage my Apps
  2. Click on your app name.
  3. Write down the OAuth 2.0 Client ID and Client Secret. You will need these later when making requests to the API.
  4. Click the link OAuth 2.0 tutorial page

When clicking the link, you will be redirected to a new page where you can test your OAuth 2.0 authorization. We will use this page to obtain a valid code to then get the access and refresh tokens.

In this page:

  1. Select Flow Type to be Authorization Code Flow
  2. Select the scopes that you will need.
  3. Check the section just below the Expires In that says - We’ve generated the authorization URL for you, all you need to do is just click on link below:.

That’s the link we want; the link that will give us the authorization code we need. Open that link and it will take you to Fitbit’s authorization page. There you just need to login with your Fitbit username and password, select the scopes you want and click the button to grant access.

Once you do that, you will be redirected to the URL you typed in the Callback URL when registering your app. If you used http://localhost, you should see an error page now but that’s OK; we just need what’s on the URL, which should look like this:

https://localhost/?code=4c8bda4345baa41e39053aaef020996d2e1a19ce#_=_

We will need this bit:

code=4c8bda4345baa41e39053aaef020996d2e1a19ce

Obtaining the tokens

With the code in hand, we can now make a request to obtain the tokens. You will submit a POST request with the following fields and values:

  • code: the code we got in the previous step
  • grant_type: authorization_code
  • client_id: the OAuth 2.0 Client ID of our application
  • redirect_uri: the URL we defined as the Callback URL when creating the application. They need to match otherwise the request will fail.

The headers must contain:

  • Authorization: Basic <pass>
  • Content-Type: application/x-www-form-urlencoded

<pass> in the Authorization Header is generated by encoding, in base64, the OAuth 2.0 Client ID, a colon, and Client Secret, like this:

ClientID:ReallyLongClientSecret

You can use an online service like https://www.base64encode.org/ to encode your pass or, if you want to do it yourself, this small Go program will do that:

package main

import (
     "encoding/base64"
     "fmt"
)

func main() {
     pass := "ClientID:ClientSecret"
     encoded := base64.StdEncoding.EncodeToString([]byte(pass))
     fmt.Printf("Encoded string: %s\n", encoded)
}

Remember to replace ClientID and ClientSecret in the program above with your own.

Once you have your pass encoded, you can use cURL to get the tokens (remember to replace the values between < and >):

curl -X POST -i -H 'Authorization: Basic <pass>'
    -H 'Content-Type: application/x-www-form-urlencoded'
    -d "clientId=<Client ID>"
    -d "grant_type=authorization_code"
    -d "redirect_uri=<Callback URL>"
    -d "code=<code>" https://api.fitbit.com/oauth2/token

If it all goes well, this request will return a JSON with the tokens, like this:

{
    "access_token": "reallyLongStringWithTheAccessToken",
    "expires_in": 28800,
    "refresh_token": "reallyLongStringWithTheRefreshToken",
    "scope": "list of selected scopes",
    "token_type": "Bearer",
    "user_id": "UserID"
}

Write down the access_token and the refresh_token. We have all we need to start playing with the Fitbit API.

Your first request

Before we start writing code, I will say that I strongly recommend that you follow Go’s recommended workspace structure if you are not doing it yet. You can read about it here: https://golang.org/doc/code.html.

Now that we have a valid authorization token, let’s write a quick application to retrieve our list of activities. Start by creating a file named activities.go at your $GOPATH/src. Then, type the following code:

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "os"
    "time"
)

func main() {
    // Transport Options. We need to set DisableCompression to false in order to be able to decode the response from the API
    tr := &http.Transport{
        MaxIdleConns:       10,
        IdleConnTimeout:    30 * time.Second,
        DisableCompression: false,
    }

    // Create an HTTP Client
    client := &http.Client{Transport: tr}

    // Create the request. Replace 2019-07-07 with the date with the activities to retrieve.
    req, _ := http.NewRequest("GET", "https://api.fitbit.com/1/user/-/activities/date/2019-07-07.json", nil)

    // Add the authorization token to the header
    req.Header.Add("Authorization", "Bearer VeryLongAuthorizationToken")

    // Make the call to the API
    resp, err := client.Do(req)

    // Errors here will be related to network or other issues when reaching the API endpoint.
    // Errors returned by the API, like an expired token, will be returned with a specific HTTP Status Code and a response body with the error.
    if err != nil {
        fmt.Printf("Error retrieving activities: %s\n", err)
        os.Exit(0)
    }

    defer resp.Body.Close()

    // Read the response body
    body, _ := ioutil.ReadAll(resp.Body)

    // and finally, print the response as returned from the API
    fmt.Printf("Raw response from get activities: %s\n\n", body)
}

Build the application by running:

go build activities.go

This will create the the application activities that you can run bu executing:

./activities

Which, if everything is working fine, should output a response like this:

Raw response from get activities: {
    "activities": [
        {
            "activityId": 17151,
            "activityParentId": 90013,
            "activityParentName": "Walk",
            "calories": 179,
            "description": "Walking less than 2 mph, strolling very slowly",
            "distance": 2.896819,
            "duration": 2700000,
            "hasStartTime": true,
            "isFavorite": false,
            "lastModified": "2019-07-24T19:48:49.000Z",
            "logId": 12345678,
            "name": "Walk",
            "startDate": "2019-07-24",
            "startTime": "12:38",
            "steps": 3872
        }
    ],
    "goals": {
        "activeMinutes": 30,
        "caloriesOut": 3125,
        "distance": 8.05,
        "steps": 10000
    },
    "summary": {
        "activeScore": -1,
        "activityCalories": 457,
        "caloriesBMR": 1905,
        "caloriesOut": 2246,
        "distances": [
            {
                "activity": "Walk",
                "distance": 2.896819
            },
            {
                "activity": "Walk",
                "distance": 4.55532
            },
            {
                "activity": "total",
                "distance": 7.45
            },
            {
                "activity": "tracker",
                "distance": 0
            },
            {
                "activity": "loggedActivities",
                "distance": 7.452139
            },
            {
                "activity": "veryActive",
                "distance": 0
            },
            {
                "activity": "moderatelyActive",
                "distance": 4.56
            },
            {
                "activity": "lightlyActive",
                "distance": 2.9
            },
            {
                "activity": "sedentaryActive",
                "distance": 0
            }
        ],
        "fairlyActiveMinutes": 42,
        "lightlyActiveMinutes": 45,
        "marginalCalories": 258,
        "sedentaryMinutes": 1353,
        "steps": 9962,
        "veryActiveMinutes": 0
    }
}

Refreshing the tokens

The application you created will run without issues until the authorization token expires. When that happens, you need to refresh the access tokens. This can be done using cURL:

curl -X POST -i -H "Authorization: Basic <pass>"
    -H "Content-Type: application/x-www-form-urlencoded"
    -d "grant_type=refresh_token"
    -d "refresh_token=<refresh token>" https://api.fitbit.com/oauth2/token

The response will be the same when obtaining the tokens for the first time, with updated access and refresh tokens. Just write these down and replace the access token in your application and it will be ready to go.

What’s next?

Let’s agree that manually obtaining the tokens every time the access token expires is a pain and, as developers, we tend to automate stuff. I’ll leave it as an exercise for the reader to implement a function that will be called to refresh the tokens whenever it expires. I’ll give you a hint, though: whenever a request is done with an expired token, the Fitbit API will return an HTTP error 401 with a response body like this:

{  
  "errors":[  
    {  
      "errorType":"expired_token",
      "message":"Some detailed message"
    }
  ],
  "success":false
}

The key here is the errorType with the value expired_token.

Now, go out there and have fun.


Cover picture by Steven Lelham via Unsplash