1. REST APIs

What is a Web API / REST API?

A REST API (REpresentational State Transfer API) is a set of functions that can be accessed over the internet. The functions are organized in a way that they can be accessed using the common protocol of the Web, HTTP (Hypertext-Transport Protocol).

By design, REST APIs are stateless, meaning:

  • calls can be made independently of one another,
  • each call contains all of the data needed to complete itself successfully, and
  • no one call depends on the next.

REST APIs are designed around resources, which consists of a URI (usually a URL), and an HTTP request method.

The methods are:

  • GET: retrieve a resource
  • POST: create a new resource
  • PUT: update a resource
  • DELETE: remove a resource
  • PATCH: update a resource with partial data

HTTP without a rest API Example

Let’s start with an example of invoking a simple HTTP request without using a REST API.

Let’s retrieve the content of the course website programmatically.

Notice the response is HTML content. This is a markup language used to create web pages and is intended for humans.

import requests
uri = "https://su-ist356-m003-fall-2025.github.io/course-home/"
response = requests.get(uri)
# Note: we're just printing the first 1000 characters here to avoid
# overwhelming output
print(response.text[:1000])
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head>

<meta charset="utf-8">
<meta name="generator" content="quarto-1.8.25">

<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">


<title>IST 356: Programming Techniques for Data Analytics – IST 356 Fall 2025</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
div.columns{display: flex; gap: min(4vw, 1.5em);}
div.column{flex: auto; overflow-x: auto;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
ul.task-list li input[type="checkbox"] {
  width: 0.8em;
  margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */ 
  vertical-align: middle;
}
</style>


<script src="site_libs/quarto-nav/quarto-nav.js"></script>
<script src="site_libs/clipboard/clipboard.min.js"></script>
<script src="site_libs/quarto-search/autocomplete.umd.js"></script>

HTTP REST API Example

This example will use the funny names API to demonstrate how to retrieve data from a REST API. Note that the code is exactly the same as the previous example, but the URL is different.

uri = "https://cent.ischool-iot.net/api/funnyname/random"
response = requests.get(uri)
print(response.text)
[{"first": "Rip", "last": "Itupp"}]

Parsing the JSON response

You can see from the example above, the response is in JSON format. Recall that JSON is a lightweight data-interchange format that is easy for humans to read and write and easy for machines to parse and generate.

Since REST API’s are for machines, it makes sense to use the JSON format. We can deserialize the JSON response into a Python dictionary or list using the json() method on requests.

uri = "https://cent.ischool-iot.net/api/funnyname/random"
response = requests.get(uri)
funny_person = response.json()
print(funny_person) # list of dict
print(funny_person[0]['first'], funny_person[0]['last'])
[{'first': 'Kenny', 'last': 'Doit'}]
Kenny Doit

Response Codes for Handling Errors

When the server returns a response to a request, included in the response is the HTTP status code, which tells the client whether or not the request worked. Unlike a regular API / function call such as print() this is necessary because there is a lot that can go wrong when you call a function over the open internet. Status codes are 3 digit numbers and the first number indicates the type of response:

  • 1xx - codes are informational. These are seldom used in web APIs.
  • 2xx - codes which begin with a 2 indicate success. The most common code is 200 - OK.
  • 3xx - codes which begin with a 3 indicate redirection - the response is not comming from the request URL you requested. For -example a 304 - Not modified means your response is coming from the browser’s cache (content already downloaded).
  • 4xx - codes which begin with a 4 indicate a client error. The most common code here is 404 - Not Found. Any 4xx errors mean the requestor did something wrong. (In this case, that’s you!)
  • 5xx - codes which begin with a 5 indicate a server error. The most common code here is 500 - Internal server error, which indicates the server could not process the request. When this happens it could be the web API’s problem or the way you made the request.

We handle errors using the raise_for_status() method on the response object. This method will raise an exception if the response is any status code other than 2xx. It’s good to raise an exception here because it will stop the program from continuing and potentially causing more problems.

# This intentionally fails with 404 - not found
uri = "https://cent.ischool-iot.net/api/funnynamez/random"
response = requests.get(uri)
response.raise_for_status()
# none of this code is relevant if the status is not 2xx
funny_person = response.json()
print(funny_person) # list of dict
print(funny_person[0]['first'], funny_person[0]['last'])
---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
Cell In[4], line 4
      2 uri = "https://cent.ischool-iot.net/api/funnynamez/random"
      3 response = requests.get(uri)
----> 4 response.raise_for_status()
      5 # none of this code is relevant if the status is not 2xx
      6 funny_person = response.json()

File /opt/hostedtoolcache/Python/3.13.7/x64/lib/python3.13/site-packages/requests/models.py:1026, in Response.raise_for_status(self)
   1021     http_error_msg = (
   1022         f"{self.status_code} Server Error: {reason} for url: {self.url}"
   1023     )
   1025 if http_error_msg:
-> 1026     raise HTTPError(http_error_msg, response=self)

HTTPError: 404 Client Error: NOT FOUND for url: https://cent.ischool-iot.net/api/funnynamez/random

Algorithm for calling any REST API in Python

  1. Prepare the request URI
    1. headers
    2. query parameters
    3. body
  2. Make the request with URI and appropriate method
  3. Check the response status code with rise_for_status()
  4. Deserialize the response into a Python object

The process is always the same, only the way the requrest is prepares and your handling of the response content will change.

Examples

In these examples we use the JSONPlaceholder API, which is a mock API for fake data. It’s mostly meant to test out API calls.

Example 1:

In this example, we get a single user’s street address.

ex1_uri = "https://jsonplaceholder.typicode.com/users/1"
response = requests.get(ex1_uri)
response.raise_for_status()
user = response.json()
print("STREET:", user['address']['street'])
STREET: Kulas Light

Example 2:

Here, we get a (random) title for a fake blog post.

ex2_uri = "https://jsonplaceholder.typicode.com/posts/1"
response = requests.get(ex2_uri)
response.raise_for_status()
post = response.json()
print("TITLE:", post['title'])
TITLE: sunt aut facere repellat provident occaecati excepturi optio reprehenderit
CautionCode Challenge 1.1

Write some Python that will:

  1. Read from the URL https://jsonplaceholder.typicode.com/users/

  2. Display the returned data in a Pandas DataFrame.

Hints:

  • Use the requests library to get the data.
  • Use Panda’s json_normalize() to convert the nested json data into a dataframe. Refer back to the second Pandas tutorial if you forget how to use that.
import requests
import pandas as pd

uri = "https://jsonplaceholder.typicode.com/users/"
response = requests.get(uri)
response.raise_for_status()
data = response.json()
df = pd.json_normalize(data)
df
id name username email phone website address.street address.suite address.city address.zipcode address.geo.lat address.geo.lng company.name company.catchPhrase company.bs
0 1 Leanne Graham Bret Sincere@april.biz 1-770-736-8031 x56442 hildegard.org Kulas Light Apt. 556 Gwenborough 92998-3874 -37.3159 81.1496 Romaguera-Crona Multi-layered client-server neural-net harness real-time e-markets
1 2 Ervin Howell Antonette Shanna@melissa.tv 010-692-6593 x09125 anastasia.net Victor Plains Suite 879 Wisokyburgh 90566-7771 -43.9509 -34.4618 Deckow-Crist Proactive didactic contingency synergize scalable supply-chains
2 3 Clementine Bauch Samantha Nathan@yesenia.net 1-463-123-4447 ramiro.info Douglas Extension Suite 847 McKenziehaven 59590-4157 -68.6102 -47.0653 Romaguera-Jacobson Face to face bifurcated interface e-enable strategic applications
3 4 Patricia Lebsack Karianne Julianne.OConner@kory.org 493-170-9623 x156 kale.biz Hoeger Mall Apt. 692 South Elvis 53919-4257 29.4572 -164.2990 Robel-Corkery Multi-tiered zero tolerance productivity transition cutting-edge web services
4 5 Chelsey Dietrich Kamren Lucio_Hettinger@annie.ca (254)954-1289 demarco.info Skiles Walks Suite 351 Roscoeview 33263 -31.8129 62.5342 Keebler LLC User-centric fault-tolerant solution revolutionize end-to-end systems
5 6 Mrs. Dennis Schulist Leopoldo_Corkery Karley_Dach@jasper.info 1-477-935-8478 x6430 ola.org Norberto Crossing Apt. 950 South Christy 23505-1337 -71.4197 71.7478 Considine-Lockman Synchronised bottom-line interface e-enable innovative applications
6 7 Kurtis Weissnat Elwyn.Skiles Telly.Hoeger@billy.biz 210.067.6132 elvis.io Rex Trail Suite 280 Howemouth 58804-1099 24.8918 21.8984 Johns Group Configurable multimedia task-force generate enterprise e-tailers
7 8 Nicholas Runolfsdottir V Maxime_Nienow Sherwood@rosamond.me 586.493.6943 x140 jacynthe.com Ellsworth Summit Suite 729 Aliyaview 45169 -14.3990 -120.7677 Abernathy Group Implemented secondary concept e-enable extensible e-tailers
8 9 Glenna Reichert Delphine Chaim_McDermott@dana.io (775)976-6794 x41206 conrad.com Dayna Park Suite 449 Bartholomebury 76495-3109 24.6463 -168.8889 Yost and Sons Switchable contextually-based project aggregate real-time technologies
9 10 Clementina DuBuque Moriah.Stanton Rey.Padberg@karina.biz 024-648-3804 ambrose.net Kattie Turnpike Suite 198 Lebsackbury 31428-2261 -38.2386 57.2232 Hoeger LLC Centralized empowering task-force target end-to-end models

Query String

The Query String is a part of the URL that is used to pass data to the server on the URL. It is appended to the end of the URL and begins with a question mark “?”. The query string is made up of a series of key-value pairs separated by an ampersand (&).

Examples:

  • /sample?x=bar => {'x': 'bar'}
  • /sample?name=John&age=30 => {'name': 'John', 'age': 'go'}
  • /sample?name=John&age=30&count=4 => {'name': 'John', 'age': '30', count: '4'}

In the Python requests library, you can pass the query string as a dictionary under the params named argument.

To illustrate, here’s an example where we use the arXiv’s REST API to retrieve information about the paper Attention Is All You Need. In the first example, we put the query information directly into the URL:

url = "http://export.arxiv.org/api/query?id_list=1706.03762"
response = requests.get(url)
response.raise_for_status()
print(response.text[:1000])
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <link href="http://arxiv.org/api/query?search_query%3D%26id_list%3D1706.03762%26start%3D0%26max_results%3D10" rel="self" type="application/atom+xml"/>
  <title type="html">ArXiv Query: search_query=&amp;id_list=1706.03762&amp;start=0&amp;max_results=10</title>
  <id>http://arxiv.org/api/zUwBFJ+vAUSpXAR7QFveSY/bZos</id>
  <updated>2025-10-27T00:00:00-04:00</updated>
  <opensearch:totalResults xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">1</opensearch:totalResults>
  <opensearch:startIndex xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">0</opensearch:startIndex>
  <opensearch:itemsPerPage xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">10</opensearch:itemsPerPage>
  <entry>
    <id>http://arxiv.org/abs/1706.03762v7</id>
    <updated>2023-08-02T00:41:18Z</updated>
    <published>2017-06-12T17:57:34Z</published>
    <title>Attention Is All You Need</title>
    <summary>  The domin

Note: The arXiv API returns XML rather than JSON. XML is just another format for transmitting data.

Here’s the same query, but this time passing the query string as a dictionary to the params argument:

url = "http://export.arxiv.org/api/query"
response = requests.get(url, params={'id_list': '1706.03762'})
response.raise_for_status()
print(response.text[:1000])
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <link href="http://arxiv.org/api/query?search_query%3D%26id_list%3D1706.03762%26start%3D0%26max_results%3D10" rel="self" type="application/atom+xml"/>
  <title type="html">ArXiv Query: search_query=&amp;id_list=1706.03762&amp;start=0&amp;max_results=10</title>
  <id>http://arxiv.org/api/zUwBFJ+vAUSpXAR7QFveSY/bZos</id>
  <updated>2025-10-27T00:00:00-04:00</updated>
  <opensearch:totalResults xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">1</opensearch:totalResults>
  <opensearch:startIndex xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">0</opensearch:startIndex>
  <opensearch:itemsPerPage xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">10</opensearch:itemsPerPage>
  <entry>
    <id>http://arxiv.org/abs/1706.03762v7</id>
    <updated>2023-08-02T00:41:18Z</updated>
    <published>2017-06-12T17:57:34Z</published>
    <title>Attention Is All You Need</title>
    <summary>  The domin

The Syracuse University Iot Portal

The Syracuse University Center for Emerging Network Technologies (CENT) created an Internet of Things (IoT) portal. The portal makes REST APIs available to IoT devices which commonly do not have the computing powert to perform these tasks, and to students so they don’t have to pay for a cloud service to use REST APIs in their projects.

To use it, go to:

https://cent.ischool-iot.net/

Then sign in with your SU NetID and password. When you sign in, you will see a box with “Your API Key”. This key is unique to you. You can use this to do API requests, as illustrated below.

Swagger and Curl

The portal has a Swagger interface at:

https://cent.ischool-iot.net/doc

This allows you to test the API’s in the browser. Swagger is a tool that helps you design, build, document, and consume REST APIs. The swagger interface shows how the API is called with a curl command, which allows you to make the same request from the command line.

To use it, you’ll need to authorize your API key:

  1. Copy your API key from cent.ischool-iot.net

  2. Now on the Swagger page click the “Authorize” button in the upper right. You will be prompted for your API key. Paste the API key you copied.

  3. You should now be able to use any of the APIs listed on the page. To try it, click one of the drop downs, then Execute. For example, click the “google” drop down, then the one for /api/google/geocode. This API provides latitude and longitude for a given location. Hit the big Execute button. Type in a location in the “location” box (e.g., “Syracuse University”) then hit the Execute button (now smaller). In the Response body, you should see a JSON string that gives information about the location, including the latitude and longitude.

Example

Let’s use swagger to call the funny names API for 10 random names, then translate the curl command into Python code.

# Translate the following code to use requests
# curl -X 'GET' \
#   'https://cent.ischool-iot.net/api/funnyname/random?n=10' \
#   -H 'accept: application/json'

uri = "https://cent.ischool-iot.net/api/funnyname/random"
params = {'n': 10}
response = requests.get(uri, params=params)
response.raise_for_status()
funny_people = response.json()
for person in funny_people:
    print(person['first'], person['last'])
Yolanda Smyland
Ginger Snaps
Sal Ladd
Sally Mander
Loren Dabucket
Ivana Sandwich
Cam Payne
Joy Touda-World
Oliver Thingz
Oren Jouglad

HTTP Headers

HTTP headers are the key / value pairs that are sent in the request or response. They are used to pass additional information about the request or response. Unlike the query string, they are not part of the URL and are not visible to the user.

In the IoT portal, the headers are used to pass the API Key which verifies who you are.

Example:

Use the random API to get 10 intergers between 1 and 100.

# Note: replace apikey with your API from the IoT portal
apikey = "GETYOUROWNKEYFROMIOTPORTAL"
uri = "https://cent.ischool-iot.net/api/random/int?"
params = { 'count': 10, 'm"in': 1, 'max': 100 }
headers = { "X-API-KEY": apikey} # goes in the header
response.raise_for_status()
print(response.url)     # see the full URL no API key there
numbers = response.json()
print(numbers)
CautionCode Challenge 1.2

Figure out how to call these in the IoT portal:

  • Google geocode API to take a location and get a latitute and longitude
  • Weather API to get the weather for a latitude and longitude

Write a Streamlit app to input a location (e.g., “Syracuse University”) and return the current weather conditions. Use the st.metric to display the temperature and humidity with units. e.g. 56°F and 80% humidity.

import requests
import streamlit as st

st.title("Streamlit Weather")
location = st.text_input("Enter a location")
if location:
    apikey = "" # replace with your API key copied from the CENT IoT portal
    headers = { "X-Api-Key": apikey }
    geourl = "https://cent.ischool-iot.net/api/google/geocode"
    params = { "location": location }
    response = requests.get(geourl, params=params, headers=headers)
    response.raise_for_status()
    geodata = response.json()
    
    lat = geodata['results'][0]['geometry']['location']["lat"]
    lon = geodata['results'][0]['geometry']['location']["lng"]
    weatherurl = "https://cent.ischool-iot.net/api/weather/current"
    params = { "lat": lat, "lon": lon, "units": "imperial" }
    response = requests.get(weatherurl, params=params, headers=headers)
    response.raise_for_status()
    weatherdata = response.json()
    temp = weatherdata['current']['temperature_2m']
    tunits = weatherdata['current_units']['temperature_2m']
    humidity = weatherdata['current']['relative_humidity_2m']
    hunits = weatherdata['current_units']['relative_humidity_2m']
    coltemp, colhumid = st.columns(2)
    coltemp.metric("Temperature", f"{temp}{tunits}")
    colhumid.metric("Humidity", f"{humidity}{hunits}")