Spotifinding your Ideal Running Playlist

Jerry Brown
6 min readNov 15, 2020

My first project for Flatiron School is called “Cadence Tunes”, a CLI program built in Ruby that allows users to create playlists based on their desired running cadence. As Openfit.com puts it, “Your brain has an inclination to sync your footsteps with the beat in your ears, which is why the best running songs can actually help you run more efficiently.” Cadence Tunes connects to the Spotify API to recommend popular songs with tempos that match the cadence that the user is aiming for.

Enter the Spotify API

In addition to all of the album, artist, playlist, and user-specific data, Spotify’s API offers tons of cool metadata on tracks taken directly from the Spotify Data Catalogue. They also have simple mechanisms to recommend songs based on that data, which is what powers Cadence Tunes. Using their API, developers have access to unique “Audio Feature” data points like danceability, energy, loudness, and, of course, tempo measured in beats per minute.

Example of Audio Feature data for Frank Ocean’s “Godspeed”
Audio Feature data for Frank Ocean’s “Godspeed”

Getting Access

Although none of this data is private information, Spotify requires developers to register their application and request authorization to access their data using a Client ID and a Client Secret. The authorization flow then returns an access_token that must be used when submitting GET requests. As this was my first time pulling data from an API, I wasn’t sure what to expect or how/if my program could easily get the necessary information from Spotify. Luckily, Spotify has clear documentation, demo GET requests, and a lot of helpful guides that explain the flow of data. Here’s an example diagram that I used to go through their Authorization Code Flow.

I decided to go with this Authorization Code Flow, which provides a refresh_token that can be used to refresh the access_token for another hour using a basic POST request. After some browsing some material on API token security, I set up a config file with the token information and made sure to use .gitignore to keep that token data off of Github. The first iteration of the API class looked something like this:

(API Class)
FILE = "./bin/config.rb"
def initialize(url)
@url = url
end
def config
JSON.parse(File.read(FILE))
end
def authenticate
access_token = config["access_token"]
@header = {
"Authorization" => "Bearer #{access_token}"
}
end
def response
response = HTTParty.get(@url, headers: self.authenticate)
end

While this worked (much to my surprise), every hour I’d received a response error that told me the access_token had expired. After a few days of working around this by manually requesting a new access token using some mysterious command line commands provided by Spotify, I plunged into creating a POST request which would use the refresh_token to request a new access token whenever it received an error. From there, the program would just need to grab that new access_token, replace the old one with the new one, and try the request again.

def refresh
refresh_token = config["refresh_token"]
encoded_id = config["client_encoded"]
options = {
headers: {
"Authorization" => "Basic #{encoded_id}"
},
body: {
"grant_type": "refresh_token",
"refresh_token": "#{refresh_token}"
}
}
end
def refresh_response
response = HTTParty.post("https://accounts.spotify.com/api/token", self.refresh)
new_token = response["access_token"]
text = File.read(FILE)
new_data = text.gsub(/#{config["access_token"]}/, "#{new_token}")
File.write("./bin/config.rb", new_data)
end
def response
response = HTTParty.get(@url, headers: self.authenticate)
if response.include?("error")
self.refresh_response
end
HTTParty.get(@url, headers: self.authenticate)
end

Seeding the Recommendation

After stumbling through the authorization process, I was eventually granted access to the Spotify Data Catalogue and could begin thinking about the flow that a Cadence Tunes user would take. Ideally, the program would be able to pull a bunch of songs from the API, capture their tempo, and then display songs to the user with tempos that the desired cadence. Since I probably shouldn’t pull all of the songs available in the Data Catalogue, I had to consider how I could filter the options down and pull a subset of songs to display.

Of the endpoints that the API provides, the “Recommendations” option stood out as a potential path forward. This endpoint allows you to retrieve Spotify generated recommendations based on seeded information. Of the potential seed options, the request must include at least one artist, genre, or song seed along with dozens of other optional seeds, including target_tempo.

Of the three required seeds, genre seemed like the most general and effective way to filter down songs for the purposes of the program. This helped inform the flow of Cadence Tunes — the user would start by inputting their desired cadence (target_tempo), select a genre that they like to run to, and then receive a display of recommended songs that they could take action on.

Seed Genres

However, not all of Spotify’s genre’s can be used as seeds for recommendations. They actually provide a specific list of seed genres that include ID’s to be used in recommendations. This meant that I’d need to set up the program to first retrieve the list of seed genres to show the user, and then use the selected genre to retrieve a collection of song recommendations.

The setup to GET the seed genres was pretty straightforward, as there are no query parameters that need to be included — it’s just a request to get the current list of seed genres. I decided to create a separate SpotifyObjectCreator class, that would act as a ‘middle-man’ between the CLI class and the API class. Here’s that setup as it relates to retrieving genres:

(SpotifyObjCreator Class)def self.access_genres
API.new(GENRE_URL).response
end
def self.create_genres
self.access_genres["genres"].map do |g_id|
name = g_id.gsub(/-/, " ").capitalize
Genre.new(name, g_id)
end

Once the genres are accessed from Spotify, I used a Genre class to initialize new instances of each genre with a Name (to show the user ) and an ID to be used to get recommendations. This way, the program only has to fetch the genre list once to populate the Genre class, and then every reference of genres after that can use local data from the Genre class.

Recommendations

At this point, the user has entered a desired running cadence, selected a genre, and now needs a list of recommended songs. Using the recommendations endpoint, the SpotifyObjectCreator class asks the API class to GET a list of song recommendations seeded with the genre and target_tempo. In addition to those seeds, a few additional parameters were hardcoded to ensure relevancy to the user and program efficiency.

(SpotifyObjCreator Class)def self.access_recommendations(genre, tempo)
url = "https://api.spotify.com/v1/recommendations?limit=50&market=us&seed_genres=#{genre}&target_tempo=#{tempo}
API.new(url).response
end
def self.create_recommendations(genre, tempo)
self.access_recommendations(genre, tempo)["tracks"].map do |t|
Song.new("#{t["name"]}", genre, "#{t["id"]}", "#{t["artists"][0]["name"]}", tempo, "#{t["popularity"]}")
end

You’ll see that the “market=” seed was set to “US” and the song “limit=” to ‘50’. This allowed my Song class to initialize 50 songs at a time, so that if the user wanted to browse through more than the initially displayed 10 songs, it wouldn’t require another request to the API.

Playlists

The program would now only require another request to the Spotify API if the user wanted to change their desired cadence or the genre. If those parameters stayed the same, the user can browse through the songs and add them to new or existing playlists, stored locally.

Overall, I learned a ton building Cadence Tunes with the Spotify API. The combination of Audio Feature track data and seeded Recommendations inspired so many additional features along the way, and I hope to implement at least a few of them in the future! Feel free to turn this solo run into a group jog by following along on Github.

--

--