Working with APIs in Python — Part 1

Rhnyewale
7 min readJan 10, 2021
Photo by Florian Olivo on Unsplash

Why APIs?

Data sets are popular resources, but they’re not always useful. Here are a few situations when data sets don’t work well:

  • The data changes frequently. It doesn’t make sense to regenerate a data set of stock prices, for example, and download it every minute. This would require a lot of bandwidth, and it would be very slow.
  • You only want a small piece of a much larger data set. Reddit comments are one example. If you want to pull only your own comments from reddit, it doesn’t make much sense to download the entire reddit database and then filter it for your comments.
  • Repeated computation. For example, Spotify has an API that can tell you the genre of a piece of music. You could create your own classifier and use it to categorize music, but you would never have as much data as Spotify has.

In these situations, an application program interface (API) is the solution. An API is a set of methods and tools that allows different applications to interact with each other. Programmers use APIs to query and retrieve data dynamically (which they can then integrate into their own apps). A client can retrieve information quickly and easily with an API.

Reddit, Spotify, Twitter, Facebook, and many other companies provide free APIs that make the information they store on their servers accessible. Other companies charge for access to their APIs.

Image from Wikipedia

Organizations host their APIs on web servers. When you type www.google.com in your browser's address bar, your computer is actually asking the www.google.com server for a web page, the server returns the page to your browser.

APIs work much the same way, except instead of your web browser asking for a web page, your program asks for data. The API usually returns this data in JavaScript Object Notation (JSON) format.

We make an API request to the web server with the data we want. The server then replies and sends it to us. In Python, we do this using the requests library.

GET Request

There are many different types of requests. The most common is a GET request, which we use to retrieve data.

OpenNotify has several API endpoints. An endpoint is a server route for retrieving specific data from an API. For example, the /comments endpoint on the reddit API might retrieve information about comments, while the /users endpoint might retrieve data about users.

The first endpoint we’ll look at on OpenNotify is the iss-now.json endpoint. This endpoint gets the current latitude and longitude position of the ISS. A data set wouldn't be a good tool for this task because the information changes often, and it involves some calculation on the server.

import requests
response = requests.get(“http://api.open-notify.org/iss-now.json")
status_code = response.status_code

The request we just made returned a status code of 200. Web servers return status codes every time they receive an API request. A status code reports what happened with a request.

Status Code

Here are some codes that are relevant to GET requests:

  • 200 — Everything went okay, and the server returned a result (if any).
  • 301 — The server is redirecting you to a different endpoint. This can happen when a company switches domain names, or when an endpoint's name has changed.
  • 401 — The server thinks you're not authenticated. This happens when you don't send the right credentials to access an API (we'll talk about this in a later mission).
  • 400 — The server thinks you made a bad request. This can happen when you don't send the information the API requires to process your request (among other things).
  • 403 — The resource you're trying to access is forbidden, and you don't have the right permissions to see it.
  • 404 — The server didn't find the resource you tried to access.
response = requests.get(‘http://api.open-notify.org/iss-pass.json')
status_code = response.status_code
status_code int(<class 'int'>)
400

We got a 400 status code, which indicates a bad request. If we look at the documentation for the OpenNotify API, we see that the ISS Pass endpoint requires two parameters.

The ISS Pass endpoint tells us when the ISS will pass over a given location on the Earth.

To request this information, we need to pass the coordinates for a specific location to the API. We do this by passing two parameters: latitude and longitude.

To do this, we can add an optional keyword argument, params, to our request. In this case, we need to pass in two parameters:

  • lat — the latitude of the location
  • lon — the longitude of the location

We can make a dictionary that contains these parameters, and then pass them into the function.

We can also do the same thing directly by adding the query parameters to the url, like this:

http://api.open-notify.org/iss-pass.json?lat=40.71&lon=-74

It’s almost always better to set up the parameters as a dictionary because the requests library we mentioned earlier takes care of certain issues, like properly formatting the query parameters.

# Set up the parameters we want to pass to the API.
# This is the latitude and longitude of New York City.
parameters = {“lat”: 37.78, “lon”: -122.41}
# Make a get request with the parameters.
response = requests.get(“http://api.open-notify.org/iss-pass.json", params=parameters)
# Print the content of the response (the data the server returned)
print(response.content)
# This gets the same data as the command above but we have passed the parameter values instead
response = requests.get(“http://api.open-notify.org/iss-pass.json?lat=40.71&lon=-74")
print(response.content)
Output:
b'{\n "message": "success", \n "request": {\n "altitude": 100, \n "datetime": 1441417753, \n "latitude": 37.78, \n "longitude": -122.41, \n "passes": 5\n }, \n "response": [\n {\n "duration": 369, \n "risetime": 1441456672\n }, \n {\n "duration": 626, \n "risetime": 1441462284\n }, \n {\n "duration": 581, \n "risetime": 1441468104\n }, \n {\n "duration": 482, \n "risetime": 1441474000\n }, \n {\n "duration": 509, \n "risetime": 1441479853\n }\n ]\n}' b'{\n "message": "success", \n "request": {\n "altitude": 100, \n "datetime": 1441417753, \n "latitude": 40.71, \n "longitude": -74.0, \n "passes": 5\n }, \n "response": [\n {\n "duration": 329, \n "risetime": 1441445639\n }, \n {\n "duration": 629, \n "risetime": 1441451226\n }, \n {\n "duration": 606, \n "risetime": 1441457027\n }, \n {\n "duration": 542, \n "risetime": 1441462894\n }, \n {\n "duration": 565, \n "risetime": 1441468731\n }\n ]\n}'

JSON

Notice that the API response we received earlier was a string. Strings are the way we pass information back and forth through APIs, but it's difficult to get the information we want out of them. How do we know how to decode the string we receive and work with it in Python?

Luckily, there’s a format called JSON. This format encodes data structures like lists and dictionaries as strings to ensure that machines can read them easily. JSON is the main format for sending and receiving data through APIs.

Python offers great support for JSON through its json library. We can convert lists and dictionaries to JSON, and vice versa. Our ISS Pass data, for example, is a dictionary encoded as a string in JSON format.

The JSON library has two main methods:

  • dumps — takes in a Python object and converts it to a string
  • loads — takes in a JSON string and converts it to a Python object
# Make a list of fast food chains.
best_food_chains = [“Taco Bell”, “Shake Shack”, “Chipotle”]
print(type(best_food_chains))
# Import the JSON library.
import json
# Use json.dumps to convert best_food_chains to a string.
best_food_chains_string = json.dumps(best_food_chains)
print(type(best_food_chains_string))
# Convert best_food_chains_string back to a list.
print(type(json.loads(best_food_chains_string)))
# Make a dictionary
fast_food_franchise = {
“Subway”: 24722,
“McDonalds”: 14098,
“Starbucks”: 10821,
“Pizza Hut”: 7600
}
# We can also dump a dictionary to a string and load it.
fast_food_franchise_string = json.dumps(fast_food_franchise)
print(type(fast_food_franchise_string))
OUTPUT:
<class 'list'>
<class 'str'>
<class 'list'>
<class 'str'>

Headers

The server sends more than a status code and the data when it generates a response. It also sends metadata with information on how it generated the data and how to decode it. This information appears in the response headers. We can access it using the .headers property.

The headers will appear as a dictionary. For now, the content-type within the headers is the most important key. It tells us the format of the response, and how to decode it. For the OpenNotify API, the format is JSON, which is why we were able to decode it with JSON earlier.

response.headers
content_type = response.headers[“content-type”]
Output:
response.headers - {'Server': 'nginx/1.10.3', 'Date': 'Thu, 07 Jan 2021 09:17:05 GMT', 'Content-Type': 'application/json', 'Content-Length': '356', 'Connection': 'keep-alive', 'access-control-allow-origin': '*'}
content_type - application/json

We looked at a basic API that didn’t require authentication, but most do. Imagine that you’re using the reddit API to pull a list of your private messages. It would be a huge privacy breach for reddit to give that information to anyone, so requiring authentication makes sense.

APIs also use authentication for rate limiting. Developers typically use APIs to build interesting applications or services. To make sure an API is available and responsive for all users, it will prevent you from making too many requests too quickly. This is called restriction rate limiting.

--

--