How to interact with APIs using Python

Often we start learning coding because we want to do something with code. For example, you might want to download a list of your sales from Shopify and put them in a spreadsheet. Or you might be looking to search through Twitter and count how many times your website has been shared by people. You may want to do these things every night, and therefore writing some code to help you is going to save you a lot of time in the long run.

This is where APIs come in.

An API is a program that accepts data from you, and gives you data back. For example, Twitter has an API.

This API accepts data (your search terms) and returns data (the tweets that match the search terms).

Twitter's API can also accept other things, such as a username, and return the user's information.

These APIs are usually designed to allow you to retrieve information that the website holds in a more program-friendly way. Instead of loading the website, you can just get the data you're interested in.

At the end of the day, websites are there for people to use. You may use the Twitter website to gain access to Twitter's data. It is displayed in a nice way so that you'll enjoy using the site and come back to it. Programs don't need niceness, so when you're interacting with an API all the program gets back is the raw data. Something like this:

{
  "tweets": [
    {
      "body": "Learning #Python today with @TecladoCode!",
      "user": "Phil Best"
    },
    {
      "body": "We've just put out a new #Python course, going from beginner to expert. #LearnPython",
      "user": "TecladoCode"
    },
    {
      "body": "Writing some code to search the Twitter API for cat images. So far just getting back dog images though... :thinking:",
      "user": "Rolf Smith"
    }
  ]
}

This is what data returned by an API might look like. Whereas on a website you'd see these tweets in cards and with colour and so on, programs only require the bare minimum. Sending text over the internet like this is much faster and cheaper than sending entire websites.

What data do you need to send an API?

Short answer: it depends on the API. The Twitter Search API requires a search term. If you want to use the Shopify Orders API to retrieve orders, you'll need the month that you want to get data from, as well as a bunch more things:

  • How many orders you want to get back;
  • Whether you want to get pending orders or all orders;
  • Whether you want to get paid, refunded, voided, or all orders;
  • Whether you want to get orders that have already shipped or not;

APIs will normally give you a lengthy reference of exactly everything you can send them so that they may send you back the data that interests you.

For completeness, here's the Shopify Orders API reference. It's confusing the first few times you look at it!

What data does an API return?

Again, it depends on the API. The Twitter Search API returns tweets that match your search term. The Shopify Orders API returns orders that happened in a Shopify store. The Shopify Customers API returns customer information for people that have made purchases in your store. And so on.

When you want to get data out of a website, it's worth checking their APIs to see if any of them can give you what you want.

Some websites don't have APIs. This can be because they haven't developed them yet, or because they don't want you to have access to their data!

Can an API do something other than take and return data?

Yes, APIs can also perform processing or storage. For example, an API might exist to which you can send an image, and it will compress it and store it on a server on the internet.

I would expect an API like this to then give you an address you can use to access the image.

For example, the imgur API does exactly this. You send it an image, and they'll store it and give you back a link. Going to that link loads the image in your browser.

Here's the imgur Image Upload API documentation.

Example interaction: OpenExchangeRates

In this example we'll be communicating with the OpenExchangeRates API to retrieve exchange rates using Python, so that we can convert one currency to another.

APIs will usually require that you register with them. This is because APIs cost money to maintain. They have to pay for servers and bandwidth, so many APIs will not be free.

Twitter, Shopify, and imgur APIs are free to use. The OpenExchangeRates API is not free, but has a free plan where you can interact with it up to 1,000 times per month.

In order to use the OpenExchangeRates API, first you must register.

Then, you must generate an App ID.

Whenever you make a request to OpenExchangeRates, you'll need to also send them your App ID. That is how the API knows that you have an account.

Once you've got your App ID, install the requests library in Python:

pip install requests

And then we can begin writing our code that interacts with OpenExchangeRates!

import requests

APP_ID = "72dba35061ifjs4cf9ad3ffbbjha8de9174"

Next, you must find what URL you need to communicate with in order to get back the exchange rate information.

The API documentation will have a list of URLs. When talking about APIs, these are called "endpoints".

API Endpoints (screenshot from https://docs.openexchangerates.org/)

We want to access the latest currency exchange information, so we will be using GET /latest.json.

Clicking on it brings us to the documentation, which tells us the URL we want to send data to is https://openexchangerates.org/api/latest.json. Let's add that to our program:

import requests

APP_ID = "72dba35061ifjs4cf9ad3ffbbjha8de9174"
ENDPOINT = "https://openexchangerates.org/api/latest.json"

Next, we can look at the examples to see how we must communicate with the API.

In the example we can see that we must make a request like so:

https://openexchangerates.org/api/latest.json?app_id=YOUR_APP_ID

This means that we have to use Python to send a GET request to this URL, making sure to include our App ID in there. There are a few different types of requests, such as GET and POST. This is just another piece of data the API expects. Depending on the request type, sometimes APIs will do different things in a single endpoint.

Then we'll get back something like this (shown also in the official documentation):

{
    disclaimer: "https://openexchangerates.org/terms/",
    license: "https://openexchangerates.org/license/",
    timestamp: 1449877801,
    base: "USD",
    rates: {
        AED: 3.672538,
        AFN: 66.809999,
        ALL: 125.716501,
        AMD: 484.902502,
        ANG: 1.788575,
        AOA: 135.295998,
        ARS: 9.750101,
        AUD: 1.390866,
        /* ... */
    }
}

Let's continue our Python code to make the request:

import requests

APP_ID = "72dba35061ifjs4cf9ad3ffbbjha8de9174"
ENDPOINT = "https://openexchangerates.org/api/latest.json"

response = requests.get(f"{ENDPOINT}?app_id={APP_ID}")
print(response.content)

Here we've built the final URL, f"{ENDPOINT}?app_id={APP_ID}", and then passed it to requests.get() which makes a GET request.

Then we print out the .content property of the response the API sends up.

Running this gives us something like:

b'{\\n  "disclaimer": "Usage subject to terms: <https://openexchangerates.org/terms>",\\n  "license": "<https://openexchangerates.org/license>",\\n  "timestamp": 1555491600,\\n  "base": "USD",\\n  "rates": {\\n    "AED": 3.673158,\\n    "AFN": 77.858,\\n    "ALL": 110,\\n    "AMD": 485.09686,\\n    "ANG": 1.865264,\\n    "AOA": 319.444,\\n    "ARS": 42.326,\\n    "AUD": 1.388915,\\n    "AWG": 1.799996,\\n    "AZN": 1.7025,\\n    "BAM": 1.730939,\\n    "BBD": 2,\\n    "BDT": 84.466,\\n    "BGN": 1.728662,\\n    "BHD": 0.377029,\\n    "BIF": 1832.588039,\\n    "BMD": 1,\\n    "BND": 1.350506,\\n    "BOB": 6.91995,\\n    "BRL": 3.905103,\\n    "BSD": 1,\\n    "BTC": 0.000191995978,\\n    "BTN": 69.571652,\\n    "BWP": 10.619946,\\n    "BYN": 2.104887,\\n    "BZD": 2.018566,\\n    "CAD": 1.332235,\\n    "CDF": 1639.646042,\\n    "CHF": 1.007504,\\n    "CLF": 0.023989,\\n    "CLP": 661.937398,\\n    "CNH": 6.68448,\\n    "CNY": 6.6885,\\n    "COP": 3135.861326,\\n    "CRC": 599.770986,\\n    "CUC": 1,\\n    "CUP": 25.75,\\n    "CVE": 97.7155,\\n    "CZK": 22.688056,\\n    "DJF": 178.05,\\n    "DKK": 6.596294,\\n    "DOP": 50.645,\\n    "DZD": 118.94,\\n    "EGP": 17.3057,\\n    "ERN": 14.996713,\\n    "ETB": 28.744636,\\n    "EUR": 0.883718,\\n    "FJD": 2.127047,\\n    "FKP": 0.766763,\\n    "GBP": 0.766763,\\n    "GEL": 2.69,\\n    "GGP": 0.766763,\\n    "GHS": 5.1291,\\n    "GIP": 0.766763,\\n    "GMD": 49.545,\\n    "GNF": 9143.139456,\\n    "GTQ": 7.638581,\\n    "GYD": 209.929837,\\n    "HKD": 7.845773,\\n    "HNL": 24.482066,\\n    "HRK": 6.57345,\\n    "HTG": 85.159527,\\n    "HUF": 282.042195,\\n    "IDR": 14011.55,\\n    "ILS": 3.575446,\\n    "IMP": 0.766763,\\n    "INR": 69.361,\\n    "IQD": 1194.896167,\\n    "IRR": 42105,\\n    "ISK": 119.860455,\\n    "JEP": 0.766763,\\n    "JMD": 129.98,\\n    "JOD": 0.708001,\\n    "JPY": 112.00975,\\n    "KES": 101.24,\\n    "KGS": 68.658487,\\n    "KHR": 4081.48868,\\n    "KMF": 436.375832,\\n    "KPW": 900,\\n    "KRW": 1132.98,\\n    "KWD": 0.304023,\\n    "KYD": 0.833511,\\n    "KZT": 380.443475,\\n    "LAK": 8663.377025,\\n    "LBP": 1509.05,\\n    "LKR": 174.64,\\n    "LRD": 166.000015,\\n    "LSL": 14.104137,\\n    "LYD": 1.391801,\\n    "MAD": 9.5813,\\n    "MDL": 17.874788,\\n    "MGA": 3622.722764,\\n    "MKD": 54.395,\\n    "MMK": 1517.16056,\\n    "MNT": 2513.624129,\\n    "MOP": 8.079097,\\n    "MRO": 357,\\n    "MRU": 36.6,\\n    "MUR": 34.78973,\\n    "MVR": 15.35504,\\n    "MWK": 728.489606,\\n    "MXN": 18.829774,\\n    "MYR": 4.140531,\\n    "MZN": 64.481897,\\n    "NAD": 14.104137,\\n    "NGN": 360.77,\\n    "NIO": 32.882163,\\n    "NOK": 8.469935,\\n    "NPR": 111.463545,\\n    "NZD": 1.484078,\\n    "OMR": 0.384954,\\n    "PAB": 1,\\n    "PEN": 3.300003,\\n    "PGK": 3.379726,\\n    "PHP": 51.715,\\n    "PKR": 141.65,\\n    "PLN": 3.778095,\\n    "PYG": 6217.386761,\\n    "QAR": 3.640738,\\n    "RON": 4.207089,\\n    "RSD": 104.24191,\\n    "RUB": 63.9525,\\n    "RWF": 906.006092,\\n    "SAR": 3.7497,\\n    "SBD": 8.171964,\\n    "SCR": 13.728573,\\n    "SDG": 47.687925,\\n    "SEK": 9.224838,\\n    "SGD": 1.352501,\\n    "SHP": 0.766763,\\n    "SLL": 8390,\\n    "SOS": 579.371256,\\n    "SRD": 7.458,\\n    "SSP": 130.2634,\\n    "STD": 21050.59961,\\n    "STN": 21.73,\\n    "SVC": 8.751192,\\n    "SYP": 514.989937,\\n    "SZL": 14.104106,\\n    "THB": 31.7655,\\n    "TJS": 9.436072,\\n    "TMT": 3.50998,\\n    "TND": 3.019007,\\n    "TOP": 2.262773,\\n    "TRY": 5.754018,\\n    "TTD": 6.78365,\\n    "TWD": 30.820944,\\n    "TZS": 2313,\\n    "UAH": 26.752,\\n    "UGX": 3740.410238,\\n    "USD": 1,\\n    "UYU": 34.130689,\\n    "UZS": 8450.117652,\\n    "VEF": 248487.642241,\\n    "VES": 4091.49358,\\n    "VND": 23168.81998,\\n    "VUV": 111.129115,\\n    "WST": 2.607179,\\n    "XAF": 579.681272,\\n    "XAG": 0.06653372,\\n    "XAU": 0.00078336,\\n    "XCD": 2.70255,\\n    "XDR": 0.717165,\\n    "XOF": 579.681272,\\n    "XPD": 0.00073341,\\n    "XPF": 105.455657,\\n    "XPT": 0.00113123,\\n    "YER": 250.300682,\\n    "ZAR": 13.954551,\\n    "ZMW": 12.293,\\n    "ZWL": 322.355011\\n  }\\n}'

This may seem like a lot of gibberish, but it is almost identical to what we saw earlier.

If we use .json() instead of .content, the requests library will take the content and turn it into a Python dictionary:

import requests

APP_ID = "72dba35061ifjs4cf9ad3ffbbjha8de9174"
ENDPOINT = "https://openexchangerates.org/api/latest.json"

response = requests.get(f"{ENDPOINT}?app_id={APP_ID}")
print(response.content)

And running that gives us:

{'disclaimer': 'Usage subject to terms: <https://openexchangerates.org/terms>', 'license': '<https://openexchangerates.org/license>', 'timestamp': 1555491600, 'base': 'USD', 'rates': {'AED': 3.673158, 'AFN': 77.858, 'ALL': 110, 'AMD': 485.09686, 'ANG': 1.865264, 'AOA': 319.444, 'ARS': 42.326, 'AUD': 1.388915, 'AWG': 1.799996, 'AZN': 1.7025, 'BAM': 1.730939, 'BBD': 2, 'BDT': 84.466, 'BGN': 1.728662, 'BHD': 0.377029, 'BIF': 1832.588039, 'BMD': 1, 'BND': 1.350506, 'BOB': 6.91995, 'BRL': 3.905103, 'BSD': 1, 'BTC': 0.000191995978, 'BTN': 69.571652, 'BWP': 10.619946, 'BYN': 2.104887, 'BZD': 2.018566, 'CAD': 1.332235, 'CDF': 1639.646042, 'CHF': 1.007504, 'CLF': 0.023989, 'CLP': 661.937398, 'CNH': 6.68448, 'CNY': 6.6885, 'COP': 3135.861326, 'CRC': 599.770986, 'CUC': 1, 'CUP': 25.75, 'CVE': 97.7155, 'CZK': 22.688056, 'DJF': 178.05, 'DKK': 6.596294, 'DOP': 50.645, 'DZD': 118.94, 'EGP': 17.3057, 'ERN': 14.996713, 'ETB': 28.744636, 'EUR': 0.883718, 'FJD': 2.127047, 'FKP': 0.766763, 'GBP': 0.766763, 'GEL': 2.69, 'GGP': 0.766763, 'GHS': 5.1291, 'GIP': 0.766763, 'GMD': 49.545, 'GNF': 9143.139456, 'GTQ': 7.638581, 'GYD': 209.929837, 'HKD': 7.845773, 'HNL': 24.482066, 'HRK': 6.57345, 'HTG': 85.159527, 'HUF': 282.042195, 'IDR': 14011.55, 'ILS': 3.575446, 'IMP': 0.766763, 'INR': 69.361, 'IQD': 1194.896167, 'IRR': 42105, 'ISK': 119.860455, 'JEP': 0.766763, 'JMD': 129.98, 'JOD': 0.708001, 'JPY': 112.00975, 'KES': 101.24, 'KGS': 68.658487, 'KHR': 4081.48868, 'KMF': 436.375832, 'KPW': 900, 'KRW': 1132.98, 'KWD': 0.304023, 'KYD': 0.833511, 'KZT': 380.443475, 'LAK': 8663.377025, 'LBP': 1509.05, 'LKR': 174.64, 'LRD': 166.000015, 'LSL': 14.104137, 'LYD': 1.391801, 'MAD': 9.5813, 'MDL': 17.874788, 'MGA': 3622.722764, 'MKD': 54.395, 'MMK': 1517.16056, 'MNT': 2513.624129, 'MOP': 8.079097, 'MRO': 357, 'MRU': 36.6, 'MUR': 34.78973, 'MVR': 15.35504, 'MWK': 728.489606, 'MXN': 18.829774, 'MYR': 4.140531, 'MZN': 64.481897, 'NAD': 14.104137, 'NGN': 360.77, 'NIO': 32.882163, 'NOK': 8.469935, 'NPR': 111.463545, 'NZD': 1.484078, 'OMR': 0.384954, 'PAB': 1, 'PEN': 3.300003, 'PGK': 3.379726, 'PHP': 51.715, 'PKR': 141.65, 'PLN': 3.778095, 'PYG': 6217.386761, 'QAR': 3.640738, 'RON': 4.207089, 'RSD': 104.24191, 'RUB': 63.9525, 'RWF': 906.006092, 'SAR': 3.7497, 'SBD': 8.171964, 'SCR': 13.728573, 'SDG': 47.687925, 'SEK': 9.224838, 'SGD': 1.352501, 'SHP': 0.766763, 'SLL': 8390, 'SOS': 579.371256, 'SRD': 7.458, 'SSP': 130.2634, 'STD': 21050.59961, 'STN': 21.73, 'SVC': 8.751192, 'SYP': 514.989937, 'SZL': 14.104106, 'THB': 31.7655, 'TJS': 9.436072, 'TMT': 3.50998, 'TND': 3.019007, 'TOP': 2.262773, 'TRY': 5.754018, 'TTD': 6.78365, 'TWD': 30.820944, 'TZS': 2313, 'UAH': 26.752, 'UGX': 3740.410238, 'USD': 1, 'UYU': 34.130689, 'UZS': 8450.117652, 'VEF': 248487.642241, 'VES': 4091.49358, 'VND': 23168.81998, 'VUV': 111.129115, 'WST': 2.607179, 'XAF': 579.681272, 'XAG': 0.06653372, 'XAU': 0.00078336, 'XCD': 2.70255, 'XDR': 0.717165, 'XOF': 579.681272, 'XPD': 0.00073341, 'XPF': 105.455657, 'XPT': 0.00113123, 'YER': 250.300682, 'ZAR': 13.954551, 'ZMW': 12.293, 'ZWL': 322.355011}}

We can now use this data to convert USD (the base) to any other currency. Let's try converting USD to GBP (Great British Pounds):

import requests

APP_ID = "72dba35061ifjs4cf9ad3ffbbjha8de9174"
ENDPOINT = "https://openexchangerates.org/api/latest.json"

response = requests.get(f"{ENDPOINT}?app_id={APP_ID}")
exchange_rates = response.json()

usd_amount = 1000
gbp_amount = usd_amount * exchange_rates['rates']['GBP']

print(f"USD{usd_amount} is GBP{gbp_amount}")

Running this will now tell us how many pounds is 1000 US dollars:

USD1000 is GBP766.7629999999999

And voilà! We've interacted with our first API.

  • An API expects some data, such as the request type (GET, POST, etc...), and any data it needs to process your request (such as an App ID, Twitter search terms, Shopify store information, etc).
  • An API returns data so that your programs can use them. APIs are not meant for people to look at (that's what the websites are for!).
  • APIs are developed to take data and respond with data—so they are very specific. You can't just send them anything and expect them to respond with valid output!
  • You can interact with APIs by using the requests library in Python, which is probably the most popular one!