Chapter 17: Project: Interacting with an API

Welcome back, aspiring Pythonista! So far, we’ve learned how to make our Python programs perform calculations, handle data, make decisions, and even manage files. That’s a solid foundation! But what if your program needs to get information from outside itself? What if it needs to talk to other services on the internet?

That’s precisely what we’ll tackle in this exciting chapter: interacting with an API. You’ll discover how to connect your Python applications to external web services, fetch data, and even send information using the power of HTTP requests. This is a fundamental skill for any modern developer, opening up a world of possibilities from building weather apps to automating social media tasks.

To get the most out of this chapter, you should be comfortable with:

  • Python basics: variables, data types (strings, numbers, lists, dictionaries).
  • Control flow: if/else statements and for loops.
  • Functions: defining and calling them.
  • Basic error handling with try/except.

Ready to make your Python program a true internet conversationalist? Let’s dive in!


What is an API, Anyway?

Before we start coding, let’s demystify “API.”

API stands for Application Programming Interface. Think of it like a menu at a restaurant.

  • You, the customer, want food (data or a service).
  • The kitchen (the external web service) has all the ingredients and chefs.
  • The menu (the API) tells you exactly what you can order (what data you can request, what actions you can perform) and how to order it (the format of your request).

You don’t need to know how the kitchen prepares the food, just what’s on the menu and how to tell the waiter (your Python code) what you want.

In the world of web development, APIs allow different software applications to talk to each other. For example, when you check the weather on your phone, your weather app is likely making an API request to a weather service to fetch the latest forecast.

How Do Programs Talk to APIs? HTTP Requests!

The primary way programs communicate with web APIs is through HTTP requests. HTTP (Hypertext Transfer Protocol) is the same protocol your web browser uses to fetch webpages.

There are different types of HTTP requests, often called “methods” or “verbs”:

  • GET: This is like asking for information. “Give me the weather for London,” or “Show me the details of post number 5.” This is the most common type for retrieving data.
  • POST: This is like sending new information to create something. “Here’s a new comment I want to add,” or “Create a new user with these details.”
  • PUT: This is for updating existing information.
  • DELETE: This is for removing information.

For this chapter, we’ll focus primarily on GET requests, as they are the simplest way to start interacting with APIs.

JSON: The Language of APIs

When you make a request to a web API, the data it sends back (the “response”) is almost always in a format called JSON (JavaScript Object Notation).

JSON is a lightweight data-interchange format that’s easy for humans to read and write, and easy for machines to parse and generate. It’s built on two structures:

  1. Key-value pairs: Like Python dictionaries! {"name": "Alice", "age": 30}
  2. Ordered lists of values: Like Python lists! ["apple", "banana", "cherry"]

You’ll see how familiar JSON feels once we start working with it in Python, as it maps beautifully to Python dictionaries and lists.

Python’s requests Library: Your API Best Friend

While Python has a built-in library (urllib) for making HTTP requests, the community overwhelmingly prefers the third-party requests library. Why? Because it’s incredibly user-friendly, intuitive, and handles many complexities for you. It’s often described as “HTTP for Humans.”

Before we can use requests, we need to install it.

Step 1: Install the requests Library

First, open your terminal or command prompt. It’s always a good practice to work within a virtual environment, but for a quick start, you can install it globally.

pip install requests

This command tells Python’s package installer (pip) to download and install the requests library.

Important Note on Python Versions: As of December 3, 2025, the latest stable release of Python is Python 3.14.1. All code examples in this chapter are designed to work perfectly with this version (and recent Python 3.x versions). If you haven’t already, make sure you’re running Python 3.14.1 or later! You can download it from the official website: https://www.python.org/downloads/


Step-by-Step Implementation: Making Your First API Call

Let’s pick a simple, free, and publicly available API to experiment with. We’ll use JSONPlaceholder, which provides fake online REST APIs for testing and prototyping. It’s perfect because it doesn’t require any authentication!

We’ll fetch a “post” (like a blog post) from JSONPlaceholder.

Step 1: Import the requests Library

Create a new Python file, say api_client.py. The very first thing we need to do is tell our Python script that we want to use the requests library.

# api_client.py

import requests

# We'll add more code here!

This line import requests makes all the functions and classes from the requests library available for us to use in our script.

Step 2: Define the API Endpoint

An API endpoint is a specific URL that your program sends requests to. For JSONPlaceholder, to get a specific post, the endpoint looks like https://jsonplaceholder.typicode.com/posts/1 (where 1 is the ID of the post).

Let’s define our URL:

# api_client.py

import requests

# The URL for the specific API resource we want to access
API_URL = "https://jsonplaceholder.typicode.com/posts/1"

# We'll add more code here!

We’ve assigned the URL string to a variable named API_URL. Using a variable makes our code cleaner and easier to modify if the URL changes.

Step 3: Make the GET Request

Now, let’s actually send the request! The requests.get() function is what we use for this. It takes the URL as its primary argument.

# api_client.py

import requests

API_URL = "https://jsonplaceholder.typicode.com/posts/1"

print(f"Attempting to fetch data from: {API_URL}")

# Make a GET request to the API_URL
response = requests.get(API_URL)

print("Request sent!")
# We'll process the response next

The requests.get(API_URL) call sends an HTTP GET request to the specified URL. The result of this operation is stored in the response variable. This response object contains all the information returned by the API server, like the data itself, status codes, headers, etc.

Step 4: Check the Status Code

Before we try to read the data, it’s crucial to check if the request was successful. APIs communicate success or failure using HTTP Status Codes.

  • 200 OK: Everything worked perfectly!
  • 404 Not Found: The requested resource doesn’t exist (e.g., trying to get posts/999999 if it doesn’t exist).
  • 400 Bad Request: The server couldn’t understand your request (e.g., missing required parameters).
  • 500 Internal Server Error: Something went wrong on the API server’s side.

The response object has a status_code attribute that holds this number.

Let’s add a check:

# api_client.py

import requests

API_URL = "https://jsonplaceholder.typicode.com/posts/1"

print(f"Attempting to fetch data from: {API_URL}")
response = requests.get(API_URL)
print("Request sent!")

# Check if the request was successful (status code 200)
if response.status_code == 200:
    print(f"Success! Status Code: {response.status_code}")
    # We'll process the data here in the next step
else:
    print(f"Oh no! Request failed with Status Code: {response.status_code}")
    print(f"Reason: {response.reason}") # Provides a human-readable reason for the status code

Run this code! You should see “Success! Status Code: 200”. If you change the API_URL to something invalid (like https://jsonplaceholder.typicode.com/posts/999999999), you’ll likely see a 404 error.

Step 5: Accessing the JSON Data

If the request was successful (status code 200), the actual data we want is usually in JSON format within the response body. The requests library makes parsing this incredibly easy with the .json() method.

# api_client.py

import requests

API_URL = "https://jsonplaceholder.typicode.com/posts/1"

print(f"Attempting to fetch data from: {API_URL}")
response = requests.get(API_URL)
print("Request sent!")

if response.status_code == 200:
    print(f"Success! Status Code: {response.status_code}")

    # Parse the JSON content of the response
    data = response.json()

    print("\nHere's the data we got:")
    print(data)

    # The JSON data is automatically converted into a Python dictionary!
    print(f"\nType of data received: {type(data)}")

    # We can now access elements like a regular dictionary
    print(f"Post Title: {data['title']}")
    print(f"Post Body: {data['body']}")
    print(f"User ID: {data['userId']}")

else:
    print(f"Oh no! Request failed with Status Code: {response.status_code}")
    print(f"Reason: {response.reason}")

Notice how response.json() magically converts the JSON string from the API into a Python dictionary. This is incredibly convenient! You can then access its elements using familiar dictionary syntax, like data['title'].

Step 6: Handling Errors More Robustly

While checking response.status_code is good, requests offers an even more convenient way to raise an HTTPError for bad responses: response.raise_for_status(). This function will do nothing if the status code is 200 (or in the 2xx range), but if it’s a 4xx or 5xx error, it will raise an exception. This is perfect for integrating with try...except blocks.

Let’s refactor our code to use this:

# api_client.py

import requests

API_URL = "https://jsonplaceholder.typicode.com/posts/1" # Let's try post 1
# API_URL = "https://jsonplaceholder.typicode.com/posts/999999999" # Uncomment to test a 404 error

print(f"Attempting to fetch data from: {API_URL}")

try:
    # Make a GET request
    response = requests.get(API_URL)

    # This will raise an HTTPError for bad responses (4xx or 5xx)
    response.raise_for_status()

    print(f"Success! Status Code: {response.status_code}")

    # Parse the JSON content
    data = response.json()

    print("\nHere's the data we got:")
    print(data)
    print(f"\nPost Title: {data['title']}")
    print(f"Post Body: {data['body']}")

except requests.exceptions.HTTPError as err:
    # Catch specific HTTP errors (like 404, 500)
    print(f"HTTP Error occurred: {err}")
    print(f"Response content: {response.text}") # You can see the raw text of the error

except requests.exceptions.ConnectionError as err:
    # Catch errors like no internet connection or DNS resolution issues
    print(f"Connection Error occurred: {err}")

except requests.exceptions.Timeout as err:
    # Catch timeout errors if the server takes too long to respond
    print(f"Timeout Error occurred: {err}")

except requests.exceptions.RequestException as err:
    # Catch any other general requests error
    print(f"An unexpected error occurred: {err}")

print("\nProgram finished.")

Now, if you uncomment the line for API_URL that points to a non-existent post (999999999), you’ll see how the try...except requests.exceptions.HTTPError block gracefully catches the error and prints a friendly message, instead of crashing your program. This is much better for robust applications!

Step 7: Adding Query Parameters

Sometimes, you don’t want a specific item by ID, but rather a filtered list of items. APIs often allow you to send query parameters to filter or sort data. These appear in the URL after a ? and are separated by & (e.g., ?userId=1&_limit=5).

The requests library makes this super easy! You can pass a dictionary of parameters to the params argument of requests.get().

Let’s fetch all posts by a specific user (e.g., userId=1).

# api_client.py (continued from previous step)

import requests

# Let's fetch multiple posts by a specific user
API_BASE_URL = "https://jsonplaceholder.typicode.com/posts" # Base URL for all posts

# Define parameters as a dictionary
parameters = {
    'userId': 1,  # We want posts by user with ID 1
    '_limit': 3   # And we only want up to 3 posts
}

print(f"\n--- Fetching posts for userId={parameters['userId']} with limit={parameters['_limit']} ---")
try:
    # Make a GET request with parameters
    response = requests.get(API_BASE_URL, params=parameters)
    response.raise_for_status() # Raise an exception for bad status codes

    print(f"Success! Status Code: {response.status_code}")

    posts_data = response.json()

    print(f"\nFetched {len(posts_data)} posts for userId 1:")
    for post in posts_data:
        print(f"- ID: {post['id']}, Title: {post['title']}")

except requests.exceptions.RequestException as err:
    print(f"Error fetching posts: {err}")

print("\nProgram finished.")

When you run this, requests automatically constructs the correct URL with the query parameters (e.g., https://jsonplaceholder.typicode.com/posts?userId=1&_limit=3). The posts_data variable will now be a list of dictionaries, each representing a post. We then iterate through this list to print out the details of each post.


Mini-Challenge: Get User Information

Alright, time for your first independent API challenge!

Challenge: The JSONPlaceholder API also has an endpoint for users: https://jsonplaceholder.typicode.com/users. Your task is to:

  1. Make a GET request to the https://jsonplaceholder.typicode.com/users endpoint.
  2. Check if the request was successful.
  3. Parse the JSON response.
  4. The response will be a list of user dictionaries. Iterate through this list and print the name and email for each user.

Hint: Remember that response.json() will return a Python list of dictionaries in this case. You can use a for loop to go through each user dictionary in the list.

What to Observe/Learn: How to handle API responses that are lists of objects, and how to extract specific pieces of information from each object in the list.

Click here for a hint if you're stuck!

Start by defining the new URL. Then, use requests.get() and response.raise_for_status() within a try...except block. Once you have the users_list = response.json(), remember that users_list is a list, so you can loop through it like for user in users_list:. Inside the loop, user will be a dictionary, allowing you to access user['name'] and user['email'].


Common Pitfalls & Troubleshooting

  1. requests.exceptions.ConnectionError: This usually means your program couldn’t reach the server.
    • Check your internet connection! Is it active?
    • Is the URL correct? A typo in https or the domain name can cause this.
    • Is the API server down? Sometimes external services have outages.
    • Did you forget pip install requests? If the import requests line gives an ModuleNotFoundError, this is your culprit.
  2. KeyError when accessing JSON data: You’re trying to access a key that doesn’t exist in the dictionary.
    • Double-check the API documentation (or print the entire data dictionary) to see the exact structure and spelling of the keys. JSON is case-sensitive! Data['title'] is different from data['title'].
    • Sometimes, an API might return an error message in a different format than its usual data. Always check the status_code first!
  3. JSONDecodeError: The API returned something that isn’t valid JSON.
    • This often happens if the status_code was not 200 (e.g., 404 or 500), and the server returned an HTML error page or plain text instead of JSON. Always use response.raise_for_status() or check response.status_code before calling response.json().
  4. Not using a virtual environment: While not a pitfall for functionality, it’s a best practice. Installing packages globally can lead to conflicts between different projects. Make it a habit to use virtual environments!

Summary

Phew! You’ve just taken a huge leap in your Python journey. Let’s recap what you’ve learned:

  • APIs (Application Programming Interfaces) allow different software to communicate, acting like a menu for services.
  • HTTP Requests are the primary way to interact with web APIs, with GET being used to retrieve data.
  • JSON (JavaScript Object Notation) is the standard data format for API responses, which Python’s requests library conveniently converts into dictionaries and lists.
  • The requests library is Python’s go-to tool for making HTTP requests, praised for its simplicity and power.
  • You learned how to install requests, make a GET request, check HTTP status codes (like 200 OK), and parse JSON responses.
  • You also learned how to handle errors robustly using try...except and response.raise_for_status(), and how to include query parameters in your requests.

You now have the fundamental tools to integrate your Python programs with countless online services! In future chapters, we’ll explore more advanced API interactions, including sending data with POST requests, handling authentication, and working with more complex API structures.

Keep experimenting with different public APIs (like the Open-Meteo weather API or various public data APIs) to solidify your understanding. The internet is your oyster!