Using The Strava API With Python — Beginner Tutorial

It’s time to do something with all that data you have on Strava

Image Generated Using DeepAI

Strava offers a powerful Web API that allows users to access and analyse data from their activities. Whether you’re a fitness enthusiast looking to track your progress or a developer interested in building a simple app that leverages activity data, the Strava API provides a wealth of possibilities.

In this tutorial, we’ll walk through how to use Python to interact with the Strava Web API. By the end of this guide, you’ll learn how to authenticate your app, gain authorised access to a user’s account, and retrieve activity data.

Before we get started, you will need a Strava account, and a Python environment set up on your machine. Any libraries used in this project that you don’t already have installed can be downloaded with pip.

Getting Started

The first step is to register an API application with Strava. You can find this option in the Strava settings in your browser, look for settings → api, or try the link below:

www.strava.com/login

You can find detailed instructions from Strava for getting started with registering your application here.

When filling out the application form, the website you provide does not matter, and the call-back domain should be set to “localhost”.

Once the setup is complete, we are provided with a client id
and a client secret. These are like the username and password for the application, and should both be kept secret.

You can copy these into your code as string literals, but instead, it is recommended that you set them as system environment variables. This way, you don’t run the risk of accidentally publishing them on GitHub for all the world to see. However, if your client secret is compromised, you can request a new one from Strava from the settings → api menu.

Setting Environment Variables

To add an environment variable in Windows 10/11, go to the start menu, search for “Edit the system environment variables” and hit enter. Once the window has opened, click on “environment variables”, then, under “user variables”, click on “new”. You can now add your client id in the pop-up window, as shown below. Do the same with the client secret. You may need to restart your system for these changes to take effect.

User Authentication Procedure

With the API application set up, we can now start the authentication procedure. Strava uses a protocol called OAuth2 for authentication. This allows users to grant our application access to their data without giving away their login credentials. Strava outline the procedure here, but the steps are as follows:

  • The application sends a request to the server. This request contains the client id (application username) and a list of scopes (which are the permissions that we are requesting).
  • The user is directed to Strava’s OAuth page prompting them to sign in and authorise the application with the permissions requested.
  • The user is then directed to the given redirect_uri (see code below) where they are given an authorisation code (embedded in the URL).
  • This code should be entered into our application. With it, we send another server request to retrieve an access token.
  • This token works as a temporary password, granting permission to access the user’s account. Whenever we want to fetch some data from a Strava profile, we will send this token, along with the request, in order to verify that we have the user’s permission.

If you just want a simple, one-time-use application, you can stop here. However, every time the application is used, the user will have to sign in and grant access to their account again. To make this more user-friendly, we need to make use of the refresh token.

When we requested the token, we were sent an access token and a refresh token. An access token is used to authorise a data retrieval request, and will expire after a set time period, usually 6 hours. A refresh token, as the name suggests, is used to refresh our access token without needing the user to sign in again and reauthorise the app.

Implementation

Let’s now take a look at the authentication procedure in Python. We will use the requests library throughout this project to send requests to the server. This library is not specific to the Strava API, it can be used with any valid web API.

Python
import os
import requests
import webbrowser
import json

def initial_authorisation_token():

  # client id and client secret can be given as string literals, but it 
  # is recommended to set them as environment variables instead, we can 
  # then access them as shown here
  client_id = os.environ['STRAVA_CLIENT_ID']
  client_secret = os.environ['STRAVA_CLIENT_SECRET']

  # url the user is directed to once they have logged in
  redirect_uri = 'http://localhost:8000'
   
  # send request to server to authorize a user
  # this will prompt the user to sign into strava and grant our application permissions
  request_url = f'http://www.strava.com/oauth/authorize?client_id={client_id}' \
                        f'&response_type=code&redirect_uri={redirect_uri}' \
                        f'&approval_prompt=force' \
                        f'&scope=profile:read_all,activity:read_all'
  
  # open url in browser
  webbrowser.open(request_url)
  
  # recieve code once user has logged in
  code = input('Insert the code from the url: ')
      
  # Get the access token
  token = requests.post(url='https://www.strava.com/api/v3/oauth/token',
                       data={'client_id': client_id,
                             'client_secret': client_secret,
                             'code': code,
                             'grant_type': 'authorization_code'})
  token = token.json()
 
  # save token for later (function shown below)
  write_token(token)

  return token
The user is required to grant permissions for the application

After logging in, the user is prompted to authorise the application, granting the permission we requested. These permissions, “profile:read_all”, and “activity:read_all”, are the scopes we sent in the request (see code above).

The exact scopes needed for a project will depend on the data we want to access from the user’s account. You can find a full list of scopes here, under “Details About Requesting Access”.

This is the code that needs to be copied into your application

After inputting the code into our application, it will send a request for the token. When we request some data from the server, we pass the access_token with it in order to authenticate the request.

This is what the token looks like (in JSON format). It contains an expiration time, access token, refresh token, and various other fields.

Python
# token format

{'token_type': 'Bearer',
 'expires_at': 1725636635,
 'expires_in': 21556,
 'refresh_token': '***************************************',
 'access_token': '***************************************',
 ... 
}

To save this token for future use, we can write it to a file:

Python
# save the token in a file

def write_token(token):

  with open('strava_token.json', 'w') as file:
    json.dump(token, file)

… and read it back later:

Python
# read token from file

def read_token():

  try:
      with open('strava_token.json', 'r') as f:
          token = json.load(f)
  except FileNotFoundError:

      # token cannot be found, so cannot be refreshed
      # instead, follow the original authorisation procedure again
      token = initial_authorisation_token()

  return token

As mentioned before, this is all we need for a simple one-time-use application. However, to make this reusable, we need to utilise the refresh token to ensure that the access token is always up to date. This should still work days or even weeks after receiving the original token.

The access token should be refreshed before it is used to request data, otherwise, if it has expired, the server will return an error message. We can check if the token has expired by comparing the current time to the expiration time given in the token.

Python
import time

# refresh the authorisation token

def refresh_token(token):

    # check if the token has expired
    if token['expires_at'] < time.time():

        # request a new access token using the refresh token
        token = requests.post(url='https://www.strava.com/api/v3/oauth/token',
                             data={'client_id': client_id,
                                   'client_secret': client_secret,
                                   'grant_type': 'refresh_token',
                                   'refresh_token': token['refresh_token']})
        token = token.json()    
        write_token(token)
        
    return token

Using the Access Token

Now we have the tools to be able to access a user’s data, let’s try importing a list of all of their activities.

We send a request to https://www.strava.com/api/v3/athlete/activities accompanied with the access token, and some other parameters. If everything is correct, it will return a list of activities.

However, much like on a website where not all results are shown in one go, this request will return only the first page of activities. We can then request page 2, then page 3, and so on, until we are returned a blank page. At this point, we will know that we have gathered the entire list.

Python
import pandas as pd

# get & update token first, uses tools described above
token = read_token()
token = refresh_token(token)


# import all user activities

activities = []
page = 1
response = []

while True:
    
    # request new page of activities
    endpoint = f"https://www.strava.com/api/v3/athlete/activities?" \
                  f"access_token={token['access_token']}&" \
                  f"page={page}&" \
                  f"per_page=50"
    
    response = requests.get(endpoint).json()

    # check if page contains activities
    if len(response):

        # retrieve some fields for each activity
        # you can see the full list of fields by looking at the response json
        activities += [{"name": i["name"],
                "distance": i["distance"],
                "type": i["type"],
                "sport_type": i["sport_type"],
                "moving_time": i["moving_time"],
                "elapsed_time": i["elapsed_time"],
                "date": i["start_date"],
                "polyline": i["map"]["summary_polyline"],
                "map_id": i["map"]["id"],
                "start_latlng": i["start_latlng"]} for i in response]
        page += 1  

    else:
      break  

# convert our activities to a DataFrame
df = pd.DataFrame(activities)
Sample of activities database

We now have a database of activity data that we can use in whichever way we want.

Usage Limits

Looking back at the Strava API settings page in the browser, we see that there is a limit to the number of requests that we can send to a server. We are restricted to only 100 read requests per 15 minutes, and 1000 per day.

Source: https://www.strava.com/settings/api

Therefore, it can be worth saving your activities to a file so that you don’t need to retrieve all of them again every time you use the application. Instead, since they are listed with the most recent ones first, you can request new ones until you find a duplicate that you already have saved.

It’s also worth noting that you can only have 1 user set up on your application. Trying to get a second user to sign in will result in an error message from the server. The only way for someone else to use this code would be to have them set up their own application, generate a client id and client secret, and use those values in your code instead. These measures are taken to prevent people making commercial applications using the Strava API.

We now have the ability to unlock insights about activities, training and performance. We can use this to create custom visualisations, analyse progress, build leader boards, and create progress trackers. The possibilities are endless.

Happy coding!


#Strava #Data Analysis #API #Python #Programming

Scroll to Top