IntermediatePython · Lesson 8 of 9

Calling Web APIs

Make HTTP requests, handle errors and timeouts, and work with JSON responses.

Most services expose data through HTTP APIs that return JSON. The popular requests library makes calls simple: requests.get(url, params=..., timeout=...).

Always set a timeout — without one, a slow server can hang your program forever. Call response.raise_for_status() to turn HTTP errors (404, 500) into exceptions, then response.json() to parse the body.

Use a requests.Session when making many calls to the same server: it reuses connections and lets you set shared headers (such as an API key) once. Keep API keys in environment variables, never in code.

TerminalShell
pip install requests
api.pyPython
import os

import requests

BASE_URL = "https://jsonplaceholder.typicode.com"


def get_user(session: requests.Session, user_id: int) -> dict:
    response = session.get(f"{BASE_URL}/users/{user_id}", timeout=5)
    response.raise_for_status()
    return response.json()


def main() -> None:
    with requests.Session() as session:
        session.headers["Authorization"] = f"Bearer {os.environ.get('API_TOKEN', 'demo')}"
        for user_id in [1, 2, 999]:
            try:
                user = get_user(session, user_id)
                print(f"{user['name']} - {user['address']['city']}")
            except requests.HTTPError as err:
                print(f"user {user_id}: HTTP {err.response.status_code}")
            except requests.RequestException as err:
                print(f"user {user_id}: network problem: {err}")


if __name__ == "__main__":
    main()

Key points

  • Always pass timeout= to network calls.
  • raise_for_status() turns HTTP error codes into exceptions.
  • Read secrets such as API keys from environment variables.

Exercise

Fetch the posts for user 1 from /posts?userId=1 using params={"userId": 1}, and print the five longest post titles. Add a retry: if a request fails with a network error, wait one second and try again up to three times.

Show solution

Try the exercise yourself first — then compare your approach with this one.

Pass query parameters with params= instead of building the URL by hand. The get_with_retry helper retries only network errors (not 404s), waiting a second between attempts, and re-raises after the third failure.

posts.pyPython
import time

import requests

BASE_URL = "https://jsonplaceholder.typicode.com"


def get_with_retry(session: requests.Session, url: str, params: dict, attempts: int = 3) -> requests.Response:
    for attempt in range(1, attempts + 1):
        try:
            response = session.get(url, params=params, timeout=5)
            response.raise_for_status()
            return response
        except (requests.ConnectionError, requests.Timeout) as err:
            if attempt == attempts:
                raise
            print(f"attempt {attempt} failed ({type(err).__name__}), retrying in 1s")
            time.sleep(1)
    raise AssertionError("unreachable")


def main() -> None:
    with requests.Session() as session:
        posts = get_with_retry(session, f"{BASE_URL}/posts", params={"userId": 1}).json()
    longest = sorted(posts, key=lambda p: len(p["title"]), reverse=True)[:5]
    for post in longest:
        print(f"{len(post['title']):>3}  {post['title']}")


if __name__ == "__main__":
    main()

Check your understanding

  1. Why always pass timeout= to requests.get?

  2. What does response.raise_for_status() do?

  3. Where should an API key come from?

  4. What is a benefit of requests.Session?

Ask AI