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!