13.1. API Fundamentals: GET, Params, and Error Handling#

import requests
import json

13.1.1. What Is an API?#

An API (Application Programming Interface) is a defined way for two programs to communicate. A REST API (Representational State Transfer) is the most common style for web APIs:

  • The client (your code) sends an HTTP request to a URL called an endpoint

  • The server processes the request and sends back an HTTP response — usually JSON

Think of it like a restaurant menu: the menu lists what you can order (endpoints), you place an order (request), and the kitchen sends back your food (response).

13.1.1.1. HTTP Methods#

Method

Typical use

GET

Retrieve data (read-only, safe to repeat)

POST

Create a new resource

PUT / PATCH

Update an existing resource

DELETE

Remove a resource

13.1.1.2. HTTP Status Codes#

Code

Meaning

200 OK

Request succeeded

201 Created

Resource was created

400 Bad Request

Client sent invalid data

401 Unauthorized

Authentication required

404 Not Found

Endpoint or resource doesn’t exist

500 Internal Server Error

Something went wrong on the server

13.1.2. Making GET Requests with requests#

The requests library is the standard Python tool for HTTP. Install it with pip install requests.

response = requests.get(url)
response.status_code   # integer status code (200, 404, …)
response.headers       # dict of response headers
response.text          # response body as a string
response.json()        # response body parsed as JSON → dict or list

We’ll use httpbin.org — a free service that echoes back your request, perfect for learning.

# httpbin.org/get echoes back details about your GET request as JSON
response = requests.get('https://httpbin.org/get')

print("Status code:", response.status_code)          # 200
print("Content-Type:", response.headers['Content-Type'])

# Parse the JSON body into a Python dict
data = response.json()
print("\nYour IP address:", data['origin'])
print("Request URL:", data['url'])
Status code: 200
Content-Type: application/json

Your IP address: 131.151.252.106
Request URL: https://httpbin.org/get

13.1.3. Query Parameters#

Most APIs accept configuration via query parameters — key/value pairs appended to the URL after ?. Instead of building the URL string manually, pass a params dict to requests.get():

# Manual:   https://example.com/search?q=python&page=2
# With params dict (same result, cleaner):
requests.get('https://example.com/search', params={'q': 'python', 'page': 2})
# httpbin.org/get echoes back the params you send
params = {'course': 'IST5551', 'chapter': 15}
response = requests.get('https://httpbin.org/get', params=params)

data = response.json()
print("Full URL requested:", data['url'])
# → https://httpbin.org/get?course=IST5551&chapter=15

print("Args received by server:")
for key, value in data['args'].items():
    print(f"  {key} = {value}")
Full URL requested: https://httpbin.org/get?course=IST5551&chapter=15
Args received by server:
  chapter = 15
  course = IST5551

13.1.4. Error Handling#

Two levels of errors can occur:

  1. Network error — no connection, timeout, DNS failure → requests raises an exception

  2. HTTP error — server responded but with a 4xx or 5xx status → response.raise_for_status() raises HTTPError

try:
    response = requests.get(url, timeout=5)
    response.raise_for_status()          # raises HTTPError for 4xx/5xx
    data = response.json()
except requests.exceptions.Timeout:
    print("Request timed out")
except requests.exceptions.HTTPError as e:
    print(f"HTTP error {e.response.status_code}: {e}")
except requests.exceptions.RequestException as e:
    print(f"Network error: {e}")

Always set a timeout — without one, your code can hang indefinitely.

def safe_get(url, params=None):
    """Make a GET request and return the JSON data, or None on failure."""
    try:
        response = requests.get(url, params=params, timeout=5)
        response.raise_for_status()
        return response.json()
    except requests.exceptions.Timeout:
        print(f"Timeout requesting {url}")
    except requests.exceptions.HTTPError as e:
        print(f"HTTP {e.response.status_code} error from {url}")
    except requests.exceptions.RequestException as e:
        print(f"Request failed: {e}")
    return None


# Test with a valid URL
data = safe_get('https://httpbin.org/get', params={'test': 'ok'})
if data:
    print("Success — status via raise_for_status passed")
    print("Args:", data['args'])

# Test with an invalid URL (404)
bad = safe_get('https://httpbin.org/status/404')
print("404 result:", bad)   # None
Success — status via raise_for_status passed
Args: {'test': 'ok'}
HTTP 404 error from https://httpbin.org/status/404
404 result: None
### Exercise: Weather API
#   The Open-Meteo API (https://api.open-meteo.com) is free — no key required.
#   Endpoint: https://api.open-meteo.com/v1/forecast
#   Required query params:
#     latitude=<float>    longitude=<float>    current=temperature_2m,wind_speed_10m
#
#   1. Use safe_get() to fetch current weather for New York (lat=40.71, lon=-74.01).
#   2. Extract and print the current temperature (°C) and wind speed (km/h).
#      The values are inside data['current'].
#   3. Fetch weather for a second city of your choice and compare temperatures.
### Your code starts here.
BASE_URL = 'https://api.open-meteo.com/v1/forecast'


### Your code ends here.
### Solution
BASE_URL = 'https://api.open-meteo.com/v1/forecast'

cities = {
    'New York':    {'latitude': 40.71, 'longitude': -74.01},
    'Los Angeles': {'latitude': 34.05, 'longitude': -118.24},
}

for city, coords in cities.items():
    params = {**coords, 'current': 'temperature_2m,wind_speed_10m'}
    data = safe_get(BASE_URL, params=params)
    if data:
        current = data['current']
        temp  = current['temperature_2m']
        wind  = current['wind_speed_10m']
        print(f"{city}: {temp}°C, wind {wind} km/h")
New York: 20.2°C, wind 27.2 km/h
Los Angeles: 22.7°C, wind 14.5 km/h

13.1.5. Summary#

Concept

Key idea

REST API

URL endpoints that accept HTTP requests and return data (usually JSON)

requests.get(url)

Make a GET request; returns a Response object

response.status_code

Integer HTTP status (200 = OK, 404 = Not Found, …)

response.json()

Parse the JSON body into a Python dict or list

params={}

Pass query parameters cleanly — requests builds the URL string

response.raise_for_status()

Raises HTTPError on 4xx/5xx responses

timeout=5

Always set a timeout to avoid hanging indefinitely

requests.exceptions.RequestException

Base class for all requests errors