Handling API responses and errors
Handling API responses and errors
APIs are a normal part of modern Python programs, especially when those programs rely on data or services outside our own code. When we make HTTP requests, we are stepping outside our process and depending on a network, a remote server, and an agreed contract. This lesson exists to help us recognize what comes back from an API call and how to react sensibly when things do not go as planned.
Inspecting HTTP response status codes
Every HTTP response includes a status code that tells us, at a high level, how the request went. A successful request typically returns a code in the 200 range, while other ranges indicate redirects, client errors, or server errors.
The requests library exposes this status code directly on the response object, which lets us make decisions before touching the response body.
import requests
response = requests.get("https://example.com/api/planets")
if response.status_code == 200:
print("Request succeeded")
else:
print("Request failed")
The status code gives us an immediate signal about whether the response is usable.
Parsing structured response data
Many APIs return structured data rather than plain text, most commonly in JSON format. When an API responds with JSON, we usually want to turn that data into Python dictionaries and lists so we can work with it naturally.
The requests library provides a convenience method for this, assuming the response body contains valid JSON.
import requests
response = requests.get("https://example.com/api/planets")
data = response.json()
print(data["name"])
Once parsed, the response data behaves like any other Python data structure.
Handling unsuccessful responses
Not every request succeeds, even when the server responds cleanly. An API may reject a request due to missing parameters, invalid input, or access restrictions. These cases often return a valid response with a non-success status code.
A common pattern is to check the status code and branch accordingly, handling success and failure as separate paths.
import requests
response = requests.get("https://example.com/api/planets")
if response.status_code != 200:
print("API returned an error")
return
planet = response.json()
print(planet["name"])
This keeps error handling explicit and prevents us from assuming a response is valid when it is not.
Detecting and responding to network errors
Sometimes a request never reaches the server or never receives a response. Network outages, DNS failures, and timeouts can all cause this. In these cases, requests raises exceptions instead of returning a response object.
We handle these situations using try and except, which allows us to detect failures outside the normal HTTP response flow.
import requests
try:
response = requests.get("https://example.com/api/planets", timeout=5)
except requests.RequestException:
print("Network error occurred")
This distinguishes network-level failures from application-level API responses.
Writing defensive code around API calls
When working with APIs, we assume less and check more. We look at status codes before parsing data, we handle exceptions around network calls, and we avoid treating external responses as guaranteed.
Defensive code makes API usage predictable and keeps failures contained, which is especially important when API calls sit inside larger workflows or automated systems.
Conclusion
We now know how to examine HTTP responses, extract structured data, and react appropriately when requests fail or never complete. With these patterns in place, API calls become safer building blocks rather than fragile points of failure. We are oriented and ready to use external services with confidence.