Skip to main content

Robust API Error Handling, Timeouts & Best Practices

In tutorial examples, API requests always succeed with 200 OK.

In the real world:

  • The server might go down (500 Server Error).
  • The user's internet cable might get disconnected (ConnectionError).
  • The API might hang forever without responding, freezing your entire application.

Here is how production engineering teams build bulletproof API clients that never crash.


1. Always Set a timeout Parameter

If a cloud server hangs, a Python request without a timeout will wait indefinitely, freezing your entire app and locking CPU threads.

Always specify timeout=5 (in seconds):

import requests

try:
# Fail fast if server does not respond within 3.5 seconds
response = requests.get("https://api.github.com/users/octocat", timeout=3.5)
except requests.exceptions.Timeout:
print("⏳ Server is taking too long to respond. Request timed out!")

2. Using raise_for_status() to Catch 4xx & 5xx HTTP Errors

By default, requests.get() does not raise a Python exception when a server returns a 404 Not Found or 500 Server Error.

Calling response.raise_for_status() automatically throws an HTTPError whenever the status code is 400 or above:

import requests

url = "https://jsonplaceholder.typicode.com/invalid-endpoint-999"

try:
response = requests.get(url, timeout=5)

# Raises an HTTPError if status code is 4xx or 5xx
response.raise_for_status()

# Process only if response was successful
data = response.json()
print("Data received:", data)

except requests.exceptions.HTTPError as http_err:
print(f"❌ HTTP Error occurred: {http_err} (Status: {response.status_code})")
except requests.exceptions.ConnectionError:
print("❌ Internet connection failure or server DNS unresolved.")
except requests.exceptions.RequestException as err:
print(f"❌ General Network Error: {err}")

3. The Hierarchy of Requests Exceptions

All requests errors inherit from the base class requests.exceptions.RequestException:

requests.exceptions.RequestException (Base Class)
├── HTTPError (404, 401, 500, etc.)
├── ConnectionError (No internet / DNS failure)
├── Timeout (Server hung)
└── TooManyRedirects

4. End-to-End Practical Mini-Project: Live Weather Client

import requests

def fetch_weather(city_name):
# Free public weather API
endpoint = f"https://api.weatherapi.com/v1/current.json"
params = {
"key": "demo_key",
"q": city_name
}

try:
response = requests.get(endpoint, params=params, timeout=5)
response.raise_for_status()
return response.json()
except requests.exceptions.Timeout:
print("Network timed out. Please try again.")
return None
except requests.exceptions.RequestException as e:
print(f"Failed to fetch weather data: {e}")
return None

Quick Summary

  • timeout=N Parameter: Crucial production guardrail preventing threads from hanging indefinitely on stalled networks.
  • response.raise_for_status(): Automatically raises an HTTPError on 4xx/5xx responses for centralized try-except handling.
  • Exception Handling: Catch specific errors (Timeout, ConnectionError, HTTPError) or catch the base RequestException.

What's Next?

Now that we know how to fetch external data from APIs, let's learn how to store, query, update, and persist structured data locally using Module 22: Database Connectivity & SQLite3!